← Back to articles

Tool Composition - fzf ripgrep zoxide

Path: Computer Tech/Terminal/Shell Integrations/Tool Composition - fzf ripgrep zoxide.mdUpdated: 2/3/2026

Tool Composition: fzf, ripgrep, and zoxide

Understanding how command-line tools compose together is fundamental to Unix philosophy. fzf, ripgrep, and zoxide are independent tools that work beautifully together but don't directly depend on each other.

The Core Principle: Unix Tool Composition

Each tool does ONE thing well, and they communicate via standard input/output.

Tool A output β†’ Tool B input β†’ Tool C input β†’ Final result

This is composition, not dependency.

The Three Tools

fzf - General-Purpose Fuzzy Finder

What it does:

  • Takes ANY text input (from stdin or a command)
  • Presents interactive fuzzy-finding interface
  • Returns selected item(s) to stdout
  • Has a built-in directory walker (doesn't need external tools by default)

What it does NOT do:

  • Search file contents (it filters whatever you give it)
  • Remember your navigation history

fzf is primarily UI, but includes basic file traversal.

fzf's Default Behavior (No External Tools Required!)

When you run fzf without piping input, it uses its built-in directory walker (since v0.47.0, March 2024):

bash
# Default behavior (no ripgrep, find, or fd needed):
fzf

# Equivalent to:
fzf --walker file,follow,hidden \
    --walker-root . \
    --walker-skip .git,node_modules

This is built into fzf itself - no external dependencies!

Historical Context: Why You Might Think fzf Uses Other Tools

Before fzf v0.47.0 (March 2024):

  • fzf DID use an external find command by default!
  • The default was: find * -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null
  • This is probably why you remember a connection!

Why the change? From the v0.47.0 changelog:

"Replaced the default find command with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms."

fzf now uses the charlievieth/fastwalk↗ library, which:

  • Outperforms external tools like find
  • Works consistently across Windows/macOS/Linux
  • Safely follows symlinks
  • Parallelizes traversal for speed

Using fzf with External Tools (Optional)

You can pipe output from other tools for more control:

bash
find . -type f | fzf        # File list from find
rg --files | fzf            # File list from ripgrep
fd --type f | fzf           # File list from fd
ls | fzf                    # File list from ls
history | fzf               # Command history from shell

ripgrep (rg) - Fast Content Search

What it does:

  • Searches file CONTENTS using regex patterns
  • Can also list files (like find)
  • Respects .gitignore by default
  • Extremely fast (written in Rust)

What it does NOT do:

  • Provide fuzzy finding interface
  • Interactive selection
  • Remember directories you visit

ripgrep is data source, not UI.

bash
# Search file contents
rg "TODO" ~/Code

# List files (similar to find)
rg --files

# Combined with fzf for interactive searching
rg --files | fzf

zoxide - Smart Directory Jumper

What it does:

  • Tracks directories you cd into
  • Ranks them by frecency (frequency + recency)
  • Provides smart jumping to frequently-visited directories

What it does NOT do:

  • Provide fuzzy finding interface
  • Search file contents
  • Generate file lists

zoxide is database + ranking algorithm, not UI.

bash
# Jump to directory (zoxide provides the list)
z maze    # Jumps to ~/Code/.../midimaze

# Interactive selection (zoxide + fzf)
zi        # Opens fzf with zoxide's directory list

How They Compose Together

Pattern 1: fzf + ripgrep

Interactive file content search:

bash
# Search contents, select file to open
rg --line-number . | fzf | cut -d: -f1 | xargs vim

# Breakdown:
# 1. rg --line-number . β†’ Search all files, show line numbers
# 2. | fzf              β†’ Fuzzy find through results
# 3. | cut -d: -f1      β†’ Extract filename only
# 4. | xargs vim        β†’ Open in vim

Interactive ripgrep reload pattern:

bash
# Live search: Every keystroke re-runs ripgrep
rg_prefix='rg --column --line-number --no-heading --color=always --smart-case'
fzf --bind 'start:reload:$rg_prefix ""' \
    --bind 'change:reload:$rg_prefix {q} || true' \
    --ansi --disabled

In this pattern:

  • ripgrep generates search results
  • fzf presents them interactively
  • Every change in fzf query β†’ ripgrep re-runs with new pattern

Full-featured interactive code search (rfv function):

bash
# ripgrep -> fzf -> nvim with quickfix support
rfv() (
  RELOAD='reload:rg --column --color=always --smart-case {q} || :'
  OPENER='if <span class="wikilink-broken" title="Page not found:  $FZF_SELECT_COUNT -eq 0 "> $FZF_SELECT_COUNT -eq 0 </span>; then
            nvim {1} +{2}     # No selection. Open the current line in Neovim.
          else
            nvim +cw -q {+f}  # Build quickfix list for the selected items.
          fi'
  fzf --disabled --ansi --multi \
      --bind "start:$RELOAD" --bind "change:$RELOAD" \
      --bind "enter:become:$OPENER" \
      --bind "ctrl-o:execute:$OPENER" \
      --bind 'alt-a:select-all,alt-d:deselect-all,ctrl-/:toggle-preview' \
      --delimiter : \
      --preview 'bat --style=full --color=always --highlight-line {2} {1}' \
      --preview-window '~4,+{2}+4/3,<80(up)' \
      --query "$*"
)

# Usage:
rfv TODO           # Search for "TODO" in current directory
rfv "function.*main"  # Regex search

Features:

  • Live ripgrep search (updates as you type)
  • Syntax-highlighted preview with bat
  • Opens in Neovim at exact line
  • Multi-select with TAB (builds Vim quickfix list)
  • CTRL-O to open file and return to fzf
  • Enter to open file and exit fzf

See fzf ripgrep integration guide↗ for full walkthrough.

Pattern 2: fzf + zoxide

Interactive directory jumping:

bash
# zoxide's interactive mode (zi) uses fzf internally
zi

# Under the hood:
# 1. zoxide query --list  β†’ Get directory database
# 2. | fzf                β†’ Fuzzy select directory
# 3. cd (selected)        β†’ Jump to selection

In Yazi's Z command:

  • zoxide provides ranked directory list
  • fzf (or similar UI) presents interactive selection
  • Yazi changes to selected directory

Pattern 3: fzf + Any Command

fzf doesn't depend on specific tools - it accepts ANY input:

bash
# Process selection
ps aux | fzf

# Git commit selection
git log --oneline | fzf

# File preview with bat
find . -type f | fzf --preview 'bat --color=always {}'

# Command history (built-in shell integration)
history | fzf

Environment Variable Configuration

fzf Configuration

Default Behavior (No Configuration)

bash
# When FZF_DEFAULT_COMMAND is NOT set:
fzf

# Uses built-in walker (no external tools needed):
# --walker file,follow,hidden
# --walker-root .
# --walker-skip .git,node_modules

fzf does NOT use ripgrep, find, or fd by default - it has its own file traversal built-in!

Overriding with External Tools (Optional)

bash
# FZF_DEFAULT_COMMAND: Override built-in walker with external command
# (Only for file/directory operations, NOT used by shell integration)
export FZF_DEFAULT_COMMAND='rg --files --hidden --follow'

# Now when you run bare `fzf`, it uses ripgrep instead:
fzf  # Same as: rg --files --hidden --follow | fzf

# Or use fd:
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'

# Or use find:
export FZF_DEFAULT_COMMAND='find . -type f'

Why override?

  • External tools might be faster for large directories
  • More control over file filtering (e.g., respecting .gitignore with ripgrep)
  • Custom file selection logic

Important: FZF_DEFAULT_COMMAND is what fzf executes to GET input, not what fzf passes its output to.

Yazi Integration

Yazi's z command:

bash
# Yazi runs this internally:
find . -type f | fzf
# Or uses fd/ripgrep if FZF_DEFAULT_COMMAND is set

Yazi's Z command:

bash
# Yazi runs this internally:
zoxide query --list | fzf
# Then: cd (selected directory)

Dependency Chart

Independent Tools (No Dependencies):
β”œβ”€β”€ fzf      ← Interactive fuzzy finder (UI layer)
β”œβ”€β”€ ripgrep  ← Content search + file listing (data source)
└── zoxide   ← Directory database (data source)

Composition Examples:
β”œβ”€β”€ ripgrep β†’ fzf     (search results β†’ interactive selection)
β”œβ”€β”€ zoxide β†’ fzf      (directory list β†’ interactive selection)
β”œβ”€β”€ find β†’ fzf        (file list β†’ interactive selection)
└── ANY β†’ fzf         (text input β†’ interactive selection)

None of these tools "use" each other in their code - they compose via stdin/stdout.

Real-World Example: Yazi's z vs Z

z Command Architecture

bash
# What Yazi does when you press 'z':
if [ -n "$FZF_DEFAULT_COMMAND" ]; then
  eval "$FZF_DEFAULT_COMMAND" | fzf
else
  find . -type f -o -type d | fzf
fi

# If result is directory β†’ cd into it
# If result is file β†’ reveal it (cd to parent, highlight file)

Tools used: fzf + (find OR ripgrep OR fd - depending on config)

Z Command Architecture

bash
# What Yazi does when you press 'Z':
zoxide query --list | fzf-or-similar-ui

# Then: cd (selected directory)

Tools used: zoxide + fzf (or built-in selector)

Common Misconceptions

❌ "fzf uses ripgrep"

Reality: fzf accepts ripgrep's output, but doesn't depend on it. You can pipe ANY text to fzf.

❌ "ripgrep needs fzf"

Reality: ripgrep is a standalone search tool. fzf just makes selection interactive.

❌ "zoxide is part of fzf"

Reality: Both are independent. zi (zoxide interactive) happens to use fzf for selection.

βœ… "These tools compose together"

Reality: They communicate via stdin/stdout following Unix philosophy.

Configuration Examples

Using ripgrep as fzf's file source

bash
# .zshrc or .bashrc
export FZF_DEFAULT_COMMAND='rg --files --hidden --follow --glob "!.git/*"'

# Now bare `fzf` uses ripgrep to generate file list
fzf

Using fd instead

bash
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'

CTRL-T in shell (uses different variable)

bash
# For shell integration's CTRL-T
export FZF_CTRL_T_COMMAND='rg --files --hidden'

# Or use --walker options (fzf built-in)
export FZF_CTRL_T_OPTS='--walker file,dir,follow,hidden --walker-skip .git,node_modules'

Interactive ripgrep (live search)

bash
# Create alias for interactive content search
alias irg='fzf --bind "start:reload:rg --column --line-number --no-heading --color=always --smart-case {q}" \
               --bind "change:reload:rg --column --line-number --no-heading --color=always --smart-case {q} || true" \
               --ansi --disabled \
               --delimiter : \
               --preview "bat --color=always {1} --highlight-line {2}"'

The Power of Composition

This architectural pattern is why Unix tools are so powerful:

  1. Modularity: Each tool has one job
  2. Flexibility: Compose in infinite combinations
  3. Interchangeability: Swap tools without breaking workflow
bash
# Same fzf interface, different data sources:
find . -type f | fzf          # File list from find
rg --files | fzf              # File list from ripgrep
fd --type f | fzf             # File list from fd
git ls-files | fzf            # File list from git
zoxide query --list | fzf     # Directory list from zoxide

The UI (fzf) stays the same - only the data source changes!

Summary Table

ToolRoleIndependenceTypical Usage
fzfInteractive UIStandalone fuzzy finderANYTHING | fzf
ripgrepSearch engineStandalone content searchrg "pattern" | fzf
zoxideDirectory trackerStandalone smart cdzoxide query --list | fzf

None depend on each other - they compose via stdin/stdout.

Related Articles

  • Yazi Navigation with fzf and zoxide - How Yazi integrates these tools
  • Unix Philosophy - Do one thing well, compose tools
  • ripgrep - Fast content search
  • fzf - Interactive fuzzy finder

Links

Official Documentation

Interactive ripgrep Examples