Skip to content
Shell & TerminalHow-To Published Updated 5 min readViews unavailable

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.

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:

Sources:

Comments