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 interactivegrepon 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 thangreporack.rgis 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):
The second command sets up shell extensions (key bindings like
brew install fzf $(brew --prefix)/opt/fzf/installCtrl+Tfor files,Alt+Cfor directories, and fuzzy completion). - Linux (Debian/Ubuntu):
You might need to manually run
sudo apt install fzf~/.fzf/installif 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
scooppackage 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):
For WSL, use the Linux instructions.
# With Scoop in PowerShell scoop install ripgrep
Once installed, verify they work:
fzf --version
rg --version0.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 cds 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.jsExample 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.pyCombining 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.rsThe 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.mdxargs 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.rsThis 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 cding 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.mdFuzzy 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'? yOther Useful Combinations
- View Git history of a file:
git log --pretty=oneline --abbrev-commit --name-only | fzf --ansi --preview 'git show {1}' - Find and
grepa 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 | fzfcan provide a text-based tree, it’s not the same. - No Drag-and-Drop: Moving or copying files requires explicit
mvorcpcommands. - 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, orstatcommands. - 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!