fzf and ripgrep Explained: Faster Searching in the Terminal

fzf and ripgrep Explained: Faster Searching in the Terminal

Two small tools change how the terminal feels more than almost anything else you can install: ripgrep (rg), which searches file contents very quickly, and fzf, which turns any list into an interactive, fuzzy-filtered picker. They are useful separately and even better together.

ripgrep

ripgrep does what grep -r does: search files for a pattern. If you are new to grep, our grep guide covers the basics that carry over.

rg 'TODO'                 # search the current directory, recursively
rg -i 'connection refused' /var/log
rg -w 'main' src/         # whole word
rg -l 'deprecated'        # file names only
rg -c 'ERROR' logs/       # count per file

What it does differently

Recursive by default. No -r needed.

Respects .gitignore. It skips files your repository ignores, like node_modules, target, and build output, which is usually what you want and is a large part of why it is fast in practice.

Skips hidden and binary files by default.

Fast. It is written in Rust, uses SIMD-accelerated searching, and searches files in parallel. On a large codebase, the difference from grep -r can be dramatic.

Readable output. File names are grouped, line numbers are shown, and matches are highlighted.

Controlling what it searches

rg -t py 'import requests'     # only Python files
rg -T js 'console.log'         # everything except JavaScript
rg -g '*.conf' 'Listen'        # glob filter
rg -g '!tests/' 'fixme'        # exclude a path
rg --hidden 'secret'           # include dotfiles
rg --no-ignore 'thing'         # ignore .gitignore rules
rg -uuu 'thing'                # search absolutely everything

rg --type-list shows the built-in file types. Globs follow the same rules as in the shell; see wildcards vs regular expressions for the difference between the glob and the search pattern.

Context and replacement

rg -C 3 'panic'                   # 3 lines of context around matches
rg 'colou?r' -r 'color' file.css  # show output with replacements
rg -F '$(var)'                    # fixed string, no regex
rg --json 'error'                 # machine-readable output

-r only rewrites output; it does not edit files. For in-place edits, pipe the list of files to sed, covered in our sed guide:

rg -l 'oldname' | xargs sed -i 's/oldname/newname/g'

When to keep using grep

grep is installed everywhere and is specified by POSIX, so scripts should still use it. It also has no surprises with ignore files. The common habit is rg at the keyboard, grep in scripts.

fzf

fzf reads lines on standard input, shows them in an interactive list, filters as you type, and prints what you choose. Its matching is fuzzy: typing nginxconf matches /etc/nginx/nginx.conf.

find . -type f | fzf
git branch | fzf
ps aux | fzf

Because it is just a filter, it composes with everything via pipes:

vim "$(fzf)"                                        # pick a file to edit
git switch "$(git branch --format='%(refname:short)' | fzf)"
kill "$(ps -eo pid,comm | fzf | awk '{print $1}')"

Search syntax

InputMatches
confFuzzy match
'confExact substring
^srcStarts with src
.md$Ends with .md
!testDoes not contain test
rs | goEither

Terms separated by spaces must all match.

The shell integration

This is where fzf earns its place. Enable its keybindings:

# bash (~/.bashrc)
eval "$(fzf --bash)"
# zsh (~/.zshrc)
source <(fzf --zsh)
# fish (~/.config/fish/config.fish)
fzf --fish | source

Older packages provide these as files under /usr/share/fzf/ or /usr/share/doc/fzf/examples/ instead; check your distro’s package.

KeyAction
Ctrl+RFuzzy-search shell history. The single biggest improvement
Ctrl+TPick files and insert their paths at the cursor
Alt+CPick a directory and cd into it
**<Tab>Fuzzy completion, such as vim **<Tab> or ssh **<Tab>

Ctrl+R alone replaces scrolling back through history with arrow keys. Our bash history guide covers making history large enough for this to shine.

Previews

fzf --preview 'cat {}'
fzf --preview 'bat --color=always {}' --preview-window=right:60%

{} is the highlighted line. Any command works as a previewer.

Using them together

ripgrep produces lists; fzf picks from them. Make fzf use ripgrep to find files, so Ctrl+T respects .gitignore and is faster:

# in ~/.bashrc or ~/.zshrc
export FZF_DEFAULT_COMMAND='rg --files --hidden -g "!.git"'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

Search file contents interactively and open the match in your editor at the right line:

rgf() {
  rg --line-number --no-heading --color=always "$@" |
    fzf --ansi --delimiter : \
        --preview 'bat --color=always {1} --highlight-line {2}' |
    awk -F: '{print "+"$2, $1}' | xargs -r -o nvim
}

rgf 'connect(' then shows every match, previews the surrounding code, and opens the one you pick. Add functions like that to your shell config; our dotfiles guide covers keeping them in sync across machines.

Installing

sudo apt install ripgrep fzf     # Debian / Ubuntu
sudo dnf install ripgrep fzf     # Fedora
sudo pacman -S ripgrep fzf       # Arch

Both are single static binaries with no dependencies, so they are also easy to drop onto servers where you cannot install packages. Their close relatives, fd (a faster, friendlier find) and bat (a cat with syntax highlighting), are worth installing at the same time; see our find guide for when the classic tool is still the better choice.

Frequently Asked Questions

What is ripgrep?

ripgrep, run as rg, is a command-line tool that searches file contents for a regular expression, like grep -r. It is usually much faster, searches recursively by default, and skips files listed in .gitignore, hidden files and binary files unless told otherwise.

What is fzf?

fzf is an interactive fuzzy finder. It reads a list of lines, lets you type a few characters to filter them with fuzzy matching, and prints the ones you select. It works with any command that produces a list, such as files, git branches, processes or shell history.

Why does ripgrep not find a file I know exists?

It is probably ignored. ripgrep skips anything matched by .gitignore, .ignore or .rgignore files, hidden files and directories, and binary files. Use —hidden to include hidden files, —no-ignore to disregard ignore files, or -uuu to search everything like grep -r does.

What do the fzf keybindings do?

Once enabled in your shell, Ctrl+T inserts selected file paths at the cursor, Ctrl+R searches shell history interactively, and Alt+C changes into a selected directory. Typing ** followed by Tab after a command also opens fzf completion for files and hosts.

Is ripgrep a replacement for grep?

For searching code and project directories interactively, yes. For scripts and standard input, grep is still the portable choice because it is installed everywhere and behaves identically across systems. Many people use rg at the keyboard and grep in scripts.

Can fzf show a preview of files?

Yes. The —preview option runs a command for the highlighted line and shows its output beside the list. For example, fzf —preview with cat or bat followed by {} displays the contents of each file as you move through the results.