How to Use fzf + ripgrep to Replace Your File Manager
For many developers, the command line is home. It’s fast, powerful, and scriptable. But even in this text-based world, we often find ourselves needing to browse files, jump between directories, or open specific documents. This is where traditional GUI file managers shine – but they often feel clunky and slow when you’re already in the terminal.
What if you could have the speed and power of the command line, combined with the interactive browsing and searching capabilities of a file manager? Enter fzf
and ripgrep
.
This post will show you how to combine these two incredibly powerful tools to navigate your filesystem, find files and content, and perform common file operations – all without leaving your terminal.
Why fzf and ripgrep?
Before we dive into the “how,” let’s quickly understand the “why.”
fzf
(Fuzzy Finder): This is an interactive Unix filter for command-line that can be used with any list-generating command. It’s blazingly fast and provides a slick fuzzy-matching interface, allowing you to quickly narrow down choices from large lists. Think of it as an interactivegrep
on steroids for selecting items.ripgrep
(rg
): A line-oriented search tool that recursively searches the current directory for a regex pattern. It’s written in Rust, which makes it incredibly fast, often significantly faster thangrep
orack
.rg
is superb at finding content or paths.
The magic happens when you pipe the output of ripgrep
(or any other listing command) into fzf
. ripgrep
efficiently finds what you’re looking for, and fzf
lets you interactively pick from the results.
Installation
Let’s get these tools on your system.
fzf Installation
- macOS (Homebrew):
brew install fzf $(brew --prefix)/opt/fzf/install
Ctrl+T
for files,Alt+C
for directories, and fuzzy completion). - Linux (Debian/Ubuntu):
sudo apt install fzf
~/.fzf/install
if you install from source or a tarball, but apt often handles key bindings. - Linux (Arch Linux):
sudo pacman -S fzf
- Windows (WSL/Git Bash): Use the Linux instructions for WSL or the
scoop
package manager for Git Bash.# With Scoop in PowerShell scoop install fzf
ripgrep Installation
- macOS (Homebrew):
brew install ripgrep
- Linux (Debian/Ubuntu):
sudo apt install ripgrep
- Linux (Arch Linux):
sudo pacman -S ripgrep
- Windows (WSL/Git Bash):
# With Scoop in PowerShell scoop install ripgrep
Once installed, verify they work:
fzf --version
rg --version
0.43.0 (ea33a59)
ripgrep 14.1.0 (rev 92a7e7195d)
Core Concepts: fzf & ripgrep Basics
Before combining them, let’s see what each can do on its own.
Basic fzf
Usage
fzf
takes any list on stdin
and provides an interactive selector.
Example 1: Selecting from ls
output
ls -F | fzf
# Interactive fzf prompt appears, allowing you to type and filter results:
> file
another_file.txt
docs/
>
# ... type 'another' ...
> another
> another_file.txt
# (Press Enter to select 'another_file.txt')
When you select an item and press Enter
, it’s printed to stdout
.
Example 2: Selecting a file to cd
into its directory (less common, but demonstrates piping)
cd $(find . -type f | fzf | xargs dirname)
This command finds all files, lets you pick one, extracts its directory, and then cd
s into it. This is useful for getting to a file’s parent directory quickly.
Basic ripgrep
Usage
ripgrep
is all about searching.
Example 1: Searching for a pattern
rg "function"
src/main.rs
20:fn main() {
35:fn my_function() {
README.md
10:This project defines a `function` to do X.
This lists matching lines with file paths and line numbers.
Example 2: Listing files that contain a pattern
The -l
(lowercase L) flag is crucial for our file manager use case. It lists only the file paths that contain a match.
rg -l "License"
LICENSE.md
src/utils.js
Example 3: Listing all files tracked by ripgrep
rg
respects .gitignore
by default. --files
lists all files rg
would search.
rg --files
.gitignore
LICENSE.md
README.md
src/main.rs
src/utils.js
tests/test_foo.py
Combining fzf and ripgrep: The Power Duo
Now for the main event: piping ripgrep
’s output to fzf
.
Use Case 1: Interactively Selecting Any File
This is a fundamental building block. We’ll use rg --files
to get a list of all non-ignored files, then pipe that to fzf
.
rg --files | fzf
# fzf prompt appears, showing all files in the current directory and subdirectories (excluding gitignored files).
> project.toml
README.md
src/
src/main.rs
tests/
tests/test_utils.py
# (Type 'main', select 'src/main.rs', press Enter)
src/main.rs
The selected file path (src/main.rs
in this case) is printed to stdout
. You can now pipe this to another command!
Use Case 2: Opening a Selected File in Your Editor
Let’s extend the previous example to open the selected file in nvim
(or code
, vim
, subl
, etc.).
rg --files | fzf | xargs nvim
# fzf prompt appears, showing all files.
> license
.gitignore
LICENSE.md
README.md
# (Type 'license', select 'LICENSE.md', press Enter)
# nvim opens LICENSE.md
xargs
is a utility that builds and executes command lines from standard input. Here, it takes the selected file path from fzf
and passes it as an argument to nvim
.
Use Case 3: Opening a File Containing Specific Content
This is where ripgrep
’s searching power truly shines. You want to open a file, but you only remember a keyword or a phrase that’s inside it.
rg -l "important_function" | fzf | xargs nvim
# fzf prompt appears, showing only files that contain "important_function".
> main
src/main.rs
src/logic.js
# (Type 'main', select 'src/main.rs', press Enter)
# nvim opens src/main.rs
This is incredibly powerful for quickly jumping to relevant files without knowing their exact names or paths.
Use Case 4: Navigating to a Directory Based on a File
Sometimes you know a file, and you want to cd
to its parent directory.
cd "$(rg --files | fzf | xargs dirname)"
# fzf prompt appears, showing all files.
> readme
README.md
docs/user_guide/README.md
# (Select 'docs/user_guide/README.md', press Enter)
# Your current directory changes to 'docs/user_guide/'
Here, dirname
extracts the directory path from the selected file path. We wrap the cd
command in $(...)
to execute the result.
Replacing Your File Manager: Practical Aliases & Functions
To make these commands truly useful, let’s create some shell functions or aliases for them. Add these to your ~/.bashrc
, ~/.zshrc
, or equivalent shell configuration file.
Fuzzy Change Directory (fcd
)
Instead of just cd
ing to a file’s directory, let’s create a general fuzzy cd
function.
Option A: Basic fcd
(using fzf
’s built-in ALT-C
functionality)
If you’ve installed fzf
with its shell extensions, you already have ALT-C
which lets you fuzzy-select directories from your recent history or subdirectories. This is often sufficient for basic navigation.
Option B: fcd
using rg --files
(finds a file, then changes to its directory)
This function lets you cd
to the directory of any file you can find via ripgrep
.
# Define this in your .bashrc/.zshrc
fcd() {
local dir
dir=$(rg --files | fzf --preview 'ls -F {}' | xargs dirname)
if [[ -n "$dir" ]]; then
cd "$dir" || return
fi
}
Now, type fcd
and press Enter. You’ll get an fzf
prompt. Select a file, and you’ll cd
into its parent directory. The --preview
option (requires fzf
0.25+) shows the contents of the selected file’s directory, which helps with context.
fcd
# fzf prompt appears, showing files for selection.
# As you move down the list, 'ls -F' output for the selected file's directory shows in the preview window.
> src/utils/helper.py
src/
src/utils/
src/utils/helper.py # Selected file
# (Preview Window):
# __init__.py
# helper.py
# constants.py
# (Press Enter to select `src/utils/helper.py`)
# Your prompt changes to reflect you are now in `src/utils/`
Fuzzy Open File (fo
)
This function allows you to quickly open any file found by ripgrep
in your default editor.
# Define this in your .bashrc/.zshrc
fo() {
local file
file=$(rg --files | fzf --preview 'bat --color=always --style=numbers --line-range=:50 {} || head -n 50 {}')
if [[ -n "$file" ]]; then
# Replace nvim with your preferred editor (e.g., code, vim, subl)
nvim "$file"
fi
}
bat
is a cat
clone with syntax highlighting and Git integration, highly recommended for the preview. If bat
isn’t found, it falls back to head
.
fo
# fzf prompt appears, showing all files.
# As you move down, 'bat' previews the file content in the right pane.
> README.md
.gitignore
LICENSE.md
README.md # Selected file
# (Preview Window):
# 1 # My Awesome Project
# 2
# 3 This project does X, Y, and Z.
# 4 ...
# (Press Enter to select `README.md`)
# nvim opens README.md
Fuzzy Grep and Edit (fge
or feg
)
This is arguably the most powerful combination. You search for a string, and then select the specific line you want to jump to in your editor. This effectively replaces grep -r "pattern"
followed by manually opening the file and searching.
# Define this in your .bashrc/.zshrc
fge() {
local result
# -n: show line numbers, -i: case-insensitive, --no-heading: cleaner output for fzf
result=$(rg -n -i --no-heading "$@" | fzf --preview 'bat --color=always {1} --highlight-line {2}')
if [[ -n "$result" ]]; then
# Extract file path and line number from the fzf selection
# Format: path:line:column:match
# We need path:line
local file_path=$(echo "$result" | awk -F: '{print $1}')
local line_num=$(echo "$result" | awk -F: '{print $2}')
# Open in nvim, jumping to the specific line
# Replace nvim with your editor, adjusting its line jump syntax if needed
nvim "+$line_num" "$file_path"
fi
}
Usage: fge <your-search-pattern>
Example: fge "error_handler"
fge "user_service"
# fzf prompt appears, showing all matching lines for "user_service".
# The preview window shows the file content with the selected line highlighted.
> src/api/handlers.py:25: user_service = UserService()
src/api/handlers.py:25: user_service = UserService() # Selected line
# (Preview Window):
# 23
# 24 def create_user_handler():
# 25 > user_service = UserService()
# 26 # ...
# 27
# (Press Enter to select `src/api/handlers.py:25: user_service = UserService()`)
# nvim opens src/api/handlers.py and jumps to line 25.
Fuzzy Delete (fdl
) - Use with Extreme Caution!
You can also use this combo to delete files. Always use -m
for multi-select in fzf
and rm -i
for interactive confirmation.
# Define this in your .bashrc/.zshrc
fdl() {
local files_to_delete
files_to_delete=$(rg --files | fzf -m --prompt="Select files to DELETE: ")
if [[ -n "$files_to_delete" ]]; then
echo "$files_to_delete" | xargs -r rm -i
else
echo "No files selected for deletion."
fi
}
The -r
flag to xargs
ensures that rm -i
is not run if files_to_delete
is empty.
fdl
# fzf prompt appears, showing all files.
# Use Tab to multi-select.
> old_logs.txt
.gitignore
old_logs.txt
temp_data.csv
README.md
# (Select 'old_logs.txt' and 'temp_data.csv' using Tab, then Enter)
rm: remove 'old_logs.txt'? y
rm: remove 'temp_data.csv'? y
Other Useful Combinations
- View Git history of a file:
git log --pretty=oneline --abbrev-commit --name-only | fzf --ansi --preview 'git show {1}'
- Find and
grep
a specific commit:git log --oneline --graph --all | fzf --ansi --no-sort --reverse --height=50% --preview "git show {1}" | awk '{print $1}' | xargs git show
Advanced Tips & Customizations
FZF_DEFAULT_COMMAND
fzf
’s default behavior is to use find . -path '*/\.*' -prune -o -type f -print -o -type d -print 2> /dev/null
or similar to list files. You can override this to use rg --files
or fd
. fd
is a great alternative to rg --files
if you strictly just want file paths, as it’s optimized for that.
# In your .bashrc/.zshrc
export FZF_DEFAULT_COMMAND='rg --files --hidden --no-ignore'
# Or, if you have fd installed (recommended for finding files)
# export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
Now, simply typing fzf
in your terminal will open an fzf
prompt pre-populated with files from rg
or fd
. This also affects Ctrl+T
if you have fzf’s key bindings installed.
FZF_DEFAULT_OPTS
You can customize fzf
’s appearance and behavior globally using this environment variable.
# In your .bashrc/.zshrc
export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border --margin=1 --padding=1 --color=bg+:#313244,bg:#1e1e2e,fg:#cdd6f4,prompt:#89b4fa,pointer:#f5e0dc,marker:#f5e0dc,hl:#f38ba8,fg+:#cdd6f4,gutter:#1e1e2e,hl+:#f38ba8 --no-info --ansi"
This example sets height, layout, adds a border, and customizes colors (using Catppuccin Macchiato palette). --no-info
removes the (n/m)
info line. --ansi
is important if your input stream contains ANSI escape codes (e.g., from git log --color
).
fd
instead of rg --files
For purely listing files and directories, fd
is often faster than rg --files
as it’s specifically designed for that purpose, whereas rg
’s --files
is a side effect of its search capabilities.
If you have fd
installed:
# Faster file listing for fzf
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
# For the fo function:
fo() {
local file
file=$(fd --type f | fzf --preview 'bat --color=always --style=numbers --line-range=:50 {} || head -n 50 {}')
if [[ -n "$file" ]]; then
nvim "$file"
fi
}
Why it’s not a full file manager replacement (and why that’s okay)
While fzf
+ ripgrep
is incredibly powerful, it’s essential to understand its limitations compared to a traditional GUI file manager like Nautilus, Finder, or Explorer:
- No Visual Tree View: You don’t get a graphical representation of your directory structure. While
tree | fzf
can provide a text-based tree, it’s not the same. - No Drag-and-Drop: Moving or copying files requires explicit
mv
orcp
commands. - No Thumbnail Previews: You can preview text content, but not images or videos directly.
- No Easy Permissions/Metadata Editing: Changing file permissions or viewing detailed metadata usually requires separate
chmod
,chown
, orstat
commands. - Steep Learning Curve (initially): It takes time to internalize the commands and build your custom functions.
However, for a command-line-centric workflow, these limitations are often minor. The sheer speed, flexibility, and scriptability of fzf
and ripgrep
far outweigh the missing GUI features for many developers. It’s a workflow enhancer, not a drop-in graphical replacement.
Conclusion
By combining the lightning-fast search capabilities of ripgrep
with the interactive filtering of fzf
, you gain an incredibly powerful way to navigate your filesystem, find files by name or content, and open them in your preferred editor – all without ever leaving the terminal.
This setup fosters a more efficient and keyboard-driven workflow, allowing you to focus on coding rather than clicking. Experiment with the examples provided, customize the functions to fit your needs, and you might just find yourself saying goodbye to your traditional file manager for good. Happy hacking!