How to Use fzf + ripgrep to Instantly Find Anything

Close-up of person typing on a laptop indoors, showcasing technology use.
Close-up of person typing on a laptop indoors, showcasing technology use.

How to Use fzf + ripgrep to Instantly Find Anything

In the vast oceans of code and files that developers and power users navigate daily, the ability to find exactly what you’re looking for – instantly – is not just a convenience, it’s a superpower. Traditional grep and find commands, while foundational, often fall short in terms of speed, user experience, and intelligence when dealing with modern, large-scale projects.

Enter fzf and ripgrep (often shortened to rg). Individually, they are formidable tools. ripgrep is a blazing-fast, intelligent line-oriented search tool, and fzf is a general-purpose command-line fuzzy finder. But when combined, they create a synergistic workflow that can transform how you interact with your filesystem, allowing you to locate files by name or content with unparalleled speed and interactive filtering.

This post will guide you through setting up, understanding, and mastering fzf and ripgrep to unlock a new level of command-line productivity.

Why ripgrep? The Speed Demon of Search

ripgrep is a command-line utility that recursively searches directories for a regex pattern. What sets it apart from traditional grep (and even other modern alternatives like ag - The Silver Searcher) is its incredible speed and sensible defaults.

Key Advantages of ripgrep

  1. Blazing Fast: ripgrep is written in Rust, which allows it to leverage highly optimized regex engines and parallel processing. It often outperforms grep, ag, and git grep by a significant margin, especially on large repositories.
  2. Smart Defaults: By default, ripgrep respects your .gitignore files and intelligently skips hidden files/directories and binary files. This means it only searches relevant code, drastically reducing search time and noise.
  3. Cross-Platform: It works seamlessly on Linux, macOS, and Windows.
  4. User-Friendly Output: Its output is clean, colorized, and easy to parse, showing filenames, line numbers, and the matching line.

Installation

Installation is straightforward across most platforms:

  • macOS (Homebrew):
    brew install ripgrep
  • Linux (apt, dnf, pacman, etc.):
    # Debian/Ubuntu
    sudo apt install ripgrep
    # Fedora
    sudo dnf install ripgrep
    # Arch Linux
    sudo pacman -S ripgrep
  • Other/Manual: You can also download pre-compiled binaries from the ripgrep GitHub releases page and place them in your PATH.

Basic ripgrep Usage

Once installed, try it out:

  • Search for a pattern in the current directory:
    rg "my_function"
  • Case-insensitive search:
    rg -i "error_handler"
  • Search only for files with a specific extension:
    rg "user_data" -g "*.js"
  • List only filenames that contain a match:
    rg --files-with-matches "TODO"

You’ll quickly notice its speed and the clean, readable output.

Why fzf? The Interactive Fuzzy Finder

fzf is a general-purpose command-line fuzzy finder. It reads list input from stdin, launches an interactive fuzzy selection UI, and writes the selected items to stdout. Its true power lies in its interactivity and flexibility, allowing it to integrate with virtually any command that outputs a list.

Key Advantages of fzf

  1. Fuzzy Matching: You don’t need to type the exact characters or even in order. fzf will “fuzzy” match your input against the list, making it incredibly forgiving and fast for recall.
  2. Interactive Interface: As you type, fzf filters the list in real-time. This provides instant feedback and allows for quick refinement of your search.
  3. Highly Customizable: fzf can be configured with various options, including preview windows, key bindings, and default commands.
  4. Seamless Integration: It’s designed to be piped with other commands (ls | fzf, git branch | fzf, history | fzf), acting as an interactive filter for virtually any list of items.

Like ripgrep, fzf is easy to install:

  • macOS (Homebrew):
    brew install fzf
    # To install fuzzy completion and key bindings, run:
    $(brew --prefix)/opt/fzf/install
    (Follow the on-screen prompts for setting up keybindings for your shell, e.g., CTRL-R for history search).
  • Linux (apt, dnf, pacman, etc.):
    # Debian/Ubuntu
    sudo apt install fzf
    # Fedora
    sudo dnf install fzf
    # Arch Linux
    sudo pacman -S fzf
    If using package managers, you might need to manually run the install script for keybindings, or check your distribution’s documentation.
  • Git Clone (recommended for latest features):
    git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
    ~/.fzf/install
    This method gives you the latest version directly from the source and prompts you to set up shell integrations.

Basic fzf Usage

Once fzf is installed and integrated into your shell, try these:

  • Filter directory contents:
    ls -l | fzf
    Type a few letters, and see how the list narrows down. Press Enter to select an item.
  • Search your shell history (CTRL-R after installation): Press CTRL-R in your terminal. This opens fzf with your command history, allowing you to fuzzy search past commands.
  • Switch Git branches:
    git branch | fzf
    (You could extend this to git checkout $(git branch | fzf) for interactive branch switching.)

The Synergistic Power: fzf + ripgrep

This is where the magic happens. We combine ripgrep’s lightning-fast search capabilities with fzf’s interactive filtering and selection. The goal is to be able to type a search query, see live results from ripgrep, preview the matching files/lines, and then instantly jump to the selected file in your editor at the correct line number.

Core Concept: Pipelining ripgrep to fzf

The simplest form of integration is to pipe ripgrep’s output directly into fzf.

rg "my_function" | fzf

This will run ripgrep for “my_function”, then pipe all matching lines into fzf. You can then fuzzy filter these lines. This is useful for quickly sifting through a large number of matches. However, it only shows the line, not the surrounding context, and doesn’t directly open the file.

The Ultimate Workflow: Interactive ripgrep with Live Preview and Editor Integration

This is the killer combination. We want fzf to act as the interactive prompt for ripgrep, allowing ripgrep to re-search as you type, show matching lines with context, and then open the selected file in your preferred editor at the correct line number.

For this, we’ll leverage fzf’s --bind 'change:reload(...)' feature and a preview window. We’ll also typically use bat for enhanced code previews, so make sure to install it:

  • macOS (Homebrew): brew install bat
  • Linux (apt, dnf, pacman, etc.): sudo apt install bat (or from bat GitHub releases)

Now, let’s define a function in your ~/.bashrc, ~/.zshrc, or equivalent shell configuration file. We’ll call it rgf (ripgrep-fzf).

# Define a function 'rgf' for ripgrep-fzf search
rgf() {
    # Check if bat is installed for enhanced previews, otherwise fall back to cat
    local PREVIEW_CMD="cat"
    if command -v bat &> /dev/null; then
        PREVIEW_CMD="bat --color=always"
    else
        echo "Warning: 'bat' not found. Install it for better previews (e.g., 'brew install bat')." >&2
    fi

    # Ensure fzf-tmux is preferred for a floating window if tmux is running
    local FZF_BIN="fzf"
    if command -v fzf-tmux &> /dev/null && [ -n "$TMUX" ]; then
        FZF_BIN="fzf-tmux -p" # -p for popup window
    fi

    # The core fzf + ripgrep command
    # - --bind 'change:reload(...)': As you type (query changes), ripgrep is re-run
    #   - `rg --column --line-number --no-heading --color=always --smart-case --fixed-strings {q} || true`
    #     - `--column`: Include column number in output (useful but not used in this specific nvim binding for simplicity).
    #     - `--line-number`: Include line number. Essential for opening files at specific lines.
    #     - `--no-heading`: Don't print filename headers (fzf handles this better in preview).
    #     - `--color=always`: Force color output, which fzf's `--ansi` option then interprets.
    #     - `--smart-case`: Search case-insensitively unless the pattern contains uppercase letters.
    #     - `--fixed-strings`: Treat pattern as a literal string, not a regex. Remove for regex search.
    #     - `{q}`: This is fzf's placeholder for the current query string you are typing.
    #     - `|| true`: Prevents fzf from erroring out if ripgrep finds no matches (ripgrep exits with 1).
    # - --ansi: Tells fzf to interpret ANSI color codes from ripgrep.
    # - --preview "$PREVIEW_CMD {1} --highlight-line {2}": Shows content of the selected file.
    #   - `{1}`: The first field extracted by `--delimiter :` (which is the filename).
    #   - `{2}`: The second field (line number).
    #   - `--highlight-line {2}`: `bat` specific, highlights the matching line.
    # - --delimiter : --nth 3..: Parses ripgrep's output.
    #   - ripgrep output format: `filename:line_number:column_number:content` (with `--column`)
    #   - `--delimiter :`: Splits each line by colon.
    #   - `--nth 3..`: Displays only the content starting from the 3rd field. This hides filename and line number in the main fzf window, keeping it clean.
    # - --bind 'enter:execute(nvim +{2} {1})': When you press Enter, execute this command.
    #   - `nvim +{2} {1}`: Opens the file (`{1}`) in Neovim at the specified line number (`{2}`).
    #     Adjust `nvim` to your preferred editor (e.g., `vim`, `code --goto`).
    # - --header: Provides helpful text at the top of the fzf window.
    $FZF_BIN \
        --bind "change:reload(rg --column --line-number --no-heading --color=always --smart-case --fixed-strings {q} || true)" \
        --ansi \
        --preview "$PREVIEW_CMD {1} --highlight-line {2}" \
        --delimiter : \
        --nth 3.. \
        --bind 'enter:execute(nvim +{2} {1})' \
        --header "Type to search with ripgrep. Enter to open in nvim. (C-d to cancel)"
}

# Add a simple alias to quickly get to your dotfiles directory and use rgf
alias dotfrg='cd ~/.dotfiles && rgf' # Adjust ~/.dotfiles to your actual dotfiles path

After adding this function to your shell configuration file (e.g., ~/.zshrc), remember to source it: source ~/.zshrc (or open a new terminal).

Now, simply type rgf and press Enter. An fzf window will pop up. Start typing your search query. As you type, ripgrep will run in the background, and fzf will display the matching lines in the main window and a preview of the selected file’s content (with the line highlighted) in the preview pane. When you find what you need, hit Enter, and it will open directly in nvim at the correct line!

Note: If you don’t use tmux, remove the fzf-tmux -p part and just use fzf. The experience will be similar, but fzf will run within your current terminal pane rather than a floating window.

Other Powerful Combinations

Beyond the live-search function, there are other useful ways to combine fzf and ripgrep:

1. Finding Files by Name (respecting .gitignore)

While find or fd (find-alternative) are common for file name searches, ripgrep can also list files. Its advantage is its built-in .gitignore awareness.

# List all tracked files (by ripgrep's definition, respecting .gitignore) and fuzzy find
rg --files | fzf --preview 'bat --color=always {}' --bind 'enter:execute(nvim {})'

# Or, if you only want files containing a specific pattern (e.g., "TODO"), then select which file to open
rg --files-with-matches "TODO" | fzf -m --preview 'bat --color=always {}' | xargs -r nvim
# -m: allows multi-selection in fzf (use Tab to select multiple, Enter to confirm)
# xargs -r nvim: opens selected files in nvim. `-r` prevents running nvim if no files selected.

This is incredibly useful for navigating large projects when you know a file contains something but might not remember its exact name.

2. Interactive Grep on a Subset of Files

You can combine ripgrep to narrow down the files, then fzf to pick which one to search, and then another ripgrep inside of that specific file.

# Find files based on a pattern, then open the chosen one and search within it.
select_and_grep() {
    local selected_file=$(rg --files-with-matches "$1" | fzf)
    if [[ -n "$selected_file" ]]; then
        # Now, you could either open the file or even run another rg specifically on it.
        # Example: Open the file and then let the user search within it manually (or use editor's search)
        nvim "$selected_file"
        # Or, to grep again *within* that file, though less common with fzf's preview already showing content
        # rg "$1" "$selected_file" | fzf
    fi
}
alias sgr='select_and_grep' # Usage: sgr "my_pattern"

Advanced Customization and Tips

  • FZF_DEFAULT_OPTS: You can set default options for fzf using this environment variable in your ~/.bashrc or ~/.zshrc. For example:

    export FZF_DEFAULT_OPTS="
      --reverse
      --height 40%
      --layout=reverse
      --ansi
      --info=inline
      --border
    "

    This makes fzf appear at the bottom, occupy 40% of the screen, and have a border.

  • FZF_DEFAULT_COMMAND: If you always want fzf to search a specific set of files by default (e.g., using rg --files instead of find), you can set this variable:

    export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git/*"'

    This would make fzf (when called without piping from another command) list files using ripgrep, including hidden files but excluding the .git directory.

  • Shell Keybindings: Ensure you’ve run the fzf/install script or manually set up keybindings. CTRL-T (for inserting selected files/directories) and CTRL-R (for history search) are incredibly useful.

  • Contextual Preview: The bat utility is key for good code previews in fzf. It handles syntax highlighting, line numbers, and automatically respects terminal width. For other file types, consider exiftool for images or pandoc for documents in the preview script.

  • Multi-Select: Remember the -m option for fzf to select multiple items (using Tab) and then Enter to output them. This is very powerful when combined with xargs. Example: rg --files-with-matches "TODO" | fzf -m | xargs -r vim

Troubleshooting and Considerations

  • Performance on Extremely Large Repos: While ripgrep is fast, searching across truly massive, unindexed repositories (e.g., kernel source trees) can still take a moment. The fzf interactive reload mechanism is best for “typing to filter” scenarios within a reasonably sized project.
  • Editor Command: Make sure to replace nvim with your actual editor command (e.g., vim, code --goto, subl).
  • Shell Compatibility: The examples primarily use Bash/Zsh syntax. If you’re on a different shell (e.g., Fish), you’ll need to adapt the function definitions accordingly. Fish shell has its own fzf integrations that are often quite seamless.
  • Regex vs. Fixed Strings: ripgrep uses regular expressions by default. For literal string searches, use the -F or --fixed-strings flag, as shown in the rgf function. This can prevent unexpected behavior with special characters.

Conclusion

The combination of fzf and ripgrep is a game-changer for anyone spending significant time in the terminal. ripgrep provides the unparalleled speed and intelligence needed for searching vast codebases, while fzf layers on an intuitive, interactive, and highly customizable fuzzy-finding interface.

By investing a small amount of time in setting up the rgf function and understanding its components, you’ll unlock a highly efficient workflow for finding files and content. No more endless grep outputs or tedious find commands. With fzf and ripgrep, you’ll instantly find anything, empowering you to navigate your digital world with precision and speed. Embrace this dynamic duo and elevate your command-line prowess!

Last updated on