How to Use fzf for Fuzzy Finding in the Shell
Setting up fzf, a general-purpose fuzzy finder that plugs into history search, file finding, and practically any list-based shell workflow.
fzf is a general-purpose command-line fuzzy finder — it takes any list of lines on standard input and lets you interactively narrow it down by typing fragments, in any order, rather than an exact match.
Step 1: install fzf
sudo apt install fzf # Debian/Ubuntu
brew install fzf # macOS
pkg install fzf # FreeBSD
Step 2: try it directly on a simple list
find . -type f | fzf
Typing a few characters of a filename narrows the list interactively, matching fuzzy substrings anywhere in each line rather than requiring an exact prefix — selecting a result with Enter prints it to standard output.
Step 3: enable fzf’s shell keybindings
# Bash: in ~/.bashrc
[ -f /usr/share/fzf/key-bindings.bash ] && source /usr/share/fzf/key-bindings.bash
# Zsh: in ~/.zshrc
[ -f /usr/share/fzf/key-bindings.zsh ] && source /usr/share/fzf/key-bindings.zsh
The exact path varies by installation method and OS — the fzf project’s own installer script offers to add these automatically during setup.
Step 4: use fuzzy history search
Ctrl-R
With fzf’s keybindings enabled, Ctrl-R replaces the shell’s normal reverse history search with fzf’s fuzzy, visually browsable version — considerably more forgiving of typos or approximate memory of a past command than the default incremental search.
Step 5: use fuzzy file finding
Ctrl-T
Inserts a fuzzy file-picker directly at your cursor position in the current command line — useful for quickly inserting a file path into a command without typing or tab-completing the full path manually.
Step 6: use fuzzy directory changing
Alt-C
Fuzzy-searches directories (not files) and cds directly into the one you select — genuinely fast for jumping into a deeply nested project directory without typing or completing the full path.
Step 7: pipe fzf into other commands for custom workflows
git branch | fzf | xargs git checkout
Because fzf is a general-purpose filter reading standard input and writing the selection to standard output, it composes with any command producing a list — git branches, running processes, package names — turning any such list into an interactively searchable one.
Step 8: customize fzf’s appearance and behavior
export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border"
FZF_DEFAULT_OPTS controls fzf’s default appearance and behavior across every invocation — useful for setting a consistent height, layout, and preview window configuration without repeating flags on every command.
Step 9: add a preview window for richer context
find . -type f | fzf --preview 'cat {}'
The --preview flag runs a specified command against the currently highlighted item, showing its output in a side pane — genuinely useful for previewing file contents before selecting one from a fuzzy file search.
Why fzf’s generality is its actual strength
Rather than being a feature bolted onto one specific tool, fzf works as a general filter over any list of text — which is exactly why it integrates cleanly with history search, file finding, git workflows, and process management alike, rather than requiring a separate specialized fuzzy-finding tool for each individual use case.
Where fzf itself came from
Junegunn Choi created fzf and published its first release, version 0.1.0, on October 31, 2013, written in Go — his stated goal was simplifying the core, recurring task of interactively filtering through some list of items in the terminal, rather than building a tool tied to any single specific use case like history search or file finding alone. That general-purpose design, reading arbitrary lines from standard input and writing the selection to standard output, is exactly what made fzf composable with essentially anything already capable of producing a plain-text list — a design decision from over a decade ago that’s directly responsible for how naturally it plugs into workflows Choi almost certainly never specifically anticipated when he first wrote it.
Step 10: replace the default file-search command with a faster, smarter one
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
By default, fzf’s Ctrl-T and no-argument invocation shell out to find, which is slow on large trees and has no built-in notion of .gitignore. Pointing FZF_DEFAULT_COMMAND at ripgrep’s --files mode (or fd, another popular choice) makes the underlying file listing itself faster and automatically ignore-aware, so fzf spends its time fuzzy-matching against a shorter, more relevant list rather than everything under the directory including build artifacts and version-control internals.
How fzf actually scores and ranks matches
fzf doesn’t just filter to matches — every match against the typed query is assigned a numeric score, and results are sorted by score rather than by input order. The default algorithm (referred to as V2 in fzf’s own documentation) is a Smith-Waterman-inspired dynamic-programming approach that rewards matches occurring at meaningful boundaries — the start of a word, immediately after a hyphen or underscore, or at a camelCase transition — over matches falling in the middle of an unrelated run of characters, and it penalizes gaps between matched characters more the longer they run on. That’s the concrete mechanism behind fzf’s results “feeling” intuitive: typing sccfg to find src/config.rs scores higher than an equal-length but boundary-blind match elsewhere in a longer path, because the algorithm is explicitly modeling where a human would expect an abbreviation to land, not simply counting matched characters.
Related:
- History Expansion and Search: How !!, Ctrl-R, and Shell History Files Actually Work
- How to Install and Use a TUI File Manager
Sources: