fzf is a general-purpose command-line fuzzy finder: pipe any line-based list into it, type a few characters, and it interactively filters the list down to the lines that match. Pick one with the arrow keys and Enter, and fzf writes your selection to standard output. That is the whole idea. It does not know what your lines mean, so it works on filenames, git branches, process IDs, log lines, anything that comes one-per-line.
The simplest possible use is fzf on its own, which finds files under the current directory and prints the one you pick:
fzfThe more useful pattern is putting fzf in the middle of a pipe. Feed it a list on stdin, filter interactively, and pipe the chosen line onward:
git branch | fzfEverything else on this page is variations on that: the shell key bindings it installs (Ctrl-T, Ctrl-R, Alt-C), the preview window, and wiring it up with fd and ripgrep so the file search skips junk.
Install fzf
The fastest path is your package manager.
# Debian / Ubuntu
sudo apt install fzf
# Fedora
sudo dnf install fzf
# Arch
sudo pacman -S fzf
# macOS (Homebrew)
brew install fzfOr clone it from GitHub, which is what I do on servers where the packaged version lags behind:
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/installThe install script offers to set up the shell key bindings and completion for bash, zsh, and fish. Say yes; the bindings are most of what makes fzf worth having. On a Homebrew install run $(brew --prefix)/opt/fzf/install to get the same setup. Check the version with fzf --version.
The core idea: it filters stdin to stdout
fzf reads lines from standard input, lets you filter them interactively, and writes the selected line(s) to standard output. So it slots into a pipe between "produce a list" and "do something with the chosen item".
# pick a running process and kill it
ps -ef | fzf | awk '{print $2}' | xargs kill
# cd into any subdirectory, fuzzy-selected
cd "$(find . -type d | fzf)"
# check out a git branch without typing the name
git branch | fzf | tr -d ' *' | xargs git checkoutThe matching is "fuzzy": the characters you type have to appear in order, but not adjacently. Typing srctmain matches src/components/Main.tsx. That is why it feels fast: you type the distinctive characters and skip the rest. A leading ' makes a term an exact match ('main matches the literal substring), ^ anchors to the start, $ to the end, and a leading ! negates. Space-separated terms are ANDed together.
Inside the picker, move the highlight with the arrow keys or Ctrl-J / Ctrl-K (down/up), press Enter to take the highlighted line, and Esc or Ctrl-G to abort without selecting anything. Those work the same whether you launched fzf yourself or it popped up from a key binding.
The shell key bindings
If you let the installer add the key bindings, you get three that fire inside any command line you are typing.
| Binding | What it does |
|---|---|
Ctrl-T | Fuzzy-find files and directories, paste the selection onto the command line |
Ctrl-R | Fuzzy-search your shell command history, paste the chosen command |
Alt-C | Fuzzy-find a directory and cd into it |
Ctrl-R is the one that changes how you use the shell. The default history search (Ctrl-R in plain bash/readline) walks backward one match at a time; fzf's version shows you all matches at once and filters as you type. I reach for it constantly to dig out a long command I ran last week without remembering the exact flags.
Ctrl-T is for building a command incrementally: type vim , hit Ctrl-T, pick the file, and the path lands on the line. Alt-C is the quick "jump somewhere deep in the tree" without typing the path. On macOS the Alt key is Option, and you may need to enable "Use Option as Meta key" in your terminal for Alt-C to register.
Preview window
fzf can run a command for the highlighted line and show the output in a side panel. This is what turns it from a picker into something you can actually inspect before committing.
fzf --preview 'cat {}'The {} is a placeholder fzf replaces with the current line. For files, cat is the obvious preview, but bat (syntax-highlighted) is nicer if you have it:
fzf --preview 'bat --color=always {}'You can preview anything. For a git branch picker, preview the recent commits on each branch:
git branch | tr -d ' *' | fzf --preview 'git log --oneline -20 {}'Resize and reposition the panel with --preview-window, for example --preview-window=right:60% or --preview-window=up:40%.
Better default source: fd and ripgrep
By default Ctrl-T and bare fzf use find to build the file list, which means they crawl .git, node_modules, and every other heavy directory. The fix is to point fzf at fd (or ripgrep), which honor .gitignore automatically. Set the default command in your shell rc:
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND='fd --type d --hidden --exclude .git'Now the file pickers skip ignored files for free, which on a real project is the difference between a fast list and one drowning in build artifacts. ripgrep works as the source too (rg --files) if you already have it and not fd. This is the same gitignore-aware behavior that makes rg worth using over grep -r; the grep cheat sheet covers that side of it.
fzf vs the tools it replaces
fzf does not replace find, grep, or readline's history search outright. It sits in front of them as the interactive selection layer.
| Tool | What it does well | Where fzf fits |
|---|---|---|
find / fd | Build the file list by criteria (name, type, time) | fzf filters that list interactively; find still does the selection logic |
grep -rn | Search file contents non-interactively, scriptably | fzf can drive an interactive grep, but scripts want grep directly |
readline Ctrl-R | Incremental reverse history search, one match at a time | fzf's Ctrl-R shows all matches and filters live |
select (bash builtin) | Menu from a fixed short list in a script | fzf scales to thousands of lines and adds typing |
The rule I use: if a human is choosing from a list, fzf. If a script is deciding from a list, the underlying tool (find, grep, awk) directly, because fzf is interactive and needs a TTY.
Caveats and gotchas
It needs a terminal. fzf is interactive, so it expects a TTY on stdout. Inside a cron job, a CI step, or a non-interactive pipe with no terminal, it will not behave the way you want. For non-interactive filtering use fzf --filter='query' (or the short -f), which prints all matches sorted by score and exits without prompting.
Multi-select needs a flag. A plain fzf returns exactly one line. Add -m (--multi) and you can mark several with Tab, then Enter to return all of them. Easy to forget when you pipe the output into xargs expecting more than one item.
The Alt key on macOS. Alt-C and the Alt- bindings depend on your terminal sending a Meta key. iTerm2 and Terminal.app both need "Option as Meta" turned on, or the binding silently does nothing.
Old packaged versions. Distro packages can trail the GitHub release by a lot, and some --preview-window and binding syntax is newer. If a documented option errors, check fzf --version against the README before assuming you typed it wrong.
Quoting in --preview. The {} placeholder is shell-quoted by fzf, so it survives spaces in filenames, but anything else in the preview command is your responsibility to quote correctly.
See also
- grep cheat sheet: the full grep reference, including the gitignore-aware
ripgrepthat pairs well with fzf as a file source - find command cheat sheet: building the file list that fzf filters, by name, type, modification time, and size
- External: junegunn/fzf on GitHub and the official fzf documentation.
FAQ
Sources
Authoritative references this article was fact-checked against.
- fzf: junegunn/fzf GitHub repository and READMEgithub.com
- fzf official documentationjunegunn.github.io





