↑ Top
Command-line fuzzy finder · user guide + tutorial

Find anything.
Compose everything.

fzf turns any stream of text into an interactive choice. Learn its small core, then use previews, field extraction, actions, and reloads to build fast terminal interfaces from ordinary Unix commands.

Offline-friendly · one HTML file Beginner → advanced Examples: Bash/Zsh on macOS & Linux Checked with fzf 0.74.2 · 2026-08-06 fzf 0.74.3 now available · not yet re-verified
producerfind · rg · git · ps
fzffilter · inspect · select
consumereditor · cd · kill · script

1. Mental model: interactive filter, not file finder

fzf reads candidate records, lets a person narrow and select them, then writes accepted records. It does not inherently know what a file, process, branch, container, or command is. That ignorance makes it reusable.

producer | fzf [options] | consumer

printf '%s\n' alpha beta gamma | fzf
git branch --format='%(refname:short)' | fzf | xargs git switch
Data flow: a producer writes records to fzf via stdin; inside fzf the query filters a match list while a preview command inspects the current item; accepted records leave via stdout to a consumer; reload can restart the producer; cancelling emits nothing and exits 130. producer fd · rg · git · ps one record per line (NUL with --read0) stdin fzf › query · fuzzy + exact + !not matches preview {} --bind KEY:ACTION · execute · become · reload stdout consumer $EDITOR · cd · kill receives accepted records only reload(cmd) restarts producer Esc / Ctrl-C → no output, exit status 130
fzf sits between producer and consumer: it never interprets records, only filters, previews, and emits them.

1 · Feed

Pipe newline-delimited items into standard input. With terminal input, fzf uses its built-in directory walker or FZF_DEFAULT_COMMAND.

2 · Find

Type a query. fzf ranks fuzzy matches, supports exact/anchored/negative terms, and can select one or many items.

3 · Emit

Enter prints accepted records to standard output. Cancel produces no record and exit status 130.

Central design questionBefore adding options, ask: “What is one candidate record, what should user see, and what exact value should downstream command receive?” Most fzf design follows from that answer.

Three layers worth separating

LayerPurposeTypical tools/options
SourceGenerate complete candidate setfd, find, rg, git, ps, SQL/JSON tools
InterfaceSearch, rank, preview, selectfzf, --multi, --preview, --bind
EffectUse accepted valuecommand substitution, loop, while read, become(...)

2. Install and enable shell integration

Prefer current package from official release channel or package manager. Distribution repositories can lag, so compare fzf --version when an option from this guide is missing.

macOS

brew install fzf
fzf --version

Linux

# Debian/Ubuntu package
sudo apt install fzf

# Or follow official binary/git install

Shell setup

Modern fzf binaries embed integration scripts. Add one line to shell startup file, then start a new shell.

ShellStartup lineProvides
Zshsource <(fzf --zsh)Ctrl+T, Ctrl+R, Alt+C, completion
Basheval "$(fzf --bash)"Key bindings and fuzzy completion
Fishfzf --fish | sourceKey bindings
Nushellfzf --nushell prints integration scriptFollow emitted-script instructions for installed version
# ~/.zshrc
source <(fzf --zsh)

# Reload now
exec zsh
Order mattersSet FZF_CTRL_T_COMMAND, FZF_ALT_C_COMMAND, and integration-specific *_OPTS before sourcing integration. Empty command variables disable corresponding bindings.

Quick health check

printf '%s\n' red green blue | fzf --height=40% --border
# type: gr   → press Enter   → terminal prints: green

3. Tutorial: from list to file launcher in 25 minutes

Work inside any small project directory. Commands inspect or open files; none modify project content unless editor saves changes.

Lab 1 · Feel fuzzy ranking
3 min

Run candidate list, type gsg, move with arrows or Ctrl+J/Ctrl+K, accept one.

printf '%s\n' git-status-guide.md getting-started.md glossary.md release-notes.md | fzf
  • Observe non-contiguous letters can match.
  • Try uppercase letter; smart-case makes query case-sensitive.
  • Press Esc; observe no output.
Lab 2 · Select a real file
4 min

Running bare fzf on terminal input invokes built-in walker. Hidden files appear; directories named .git and node_modules are skipped by default.

fzf --height=70% --layout=reverse --border --prompt='file › '

# Explicit source with fd (if installed)
fd --type f --hidden --exclude .git | fzf --scheme=path

Why explicit source? Reproducible scope, easy exclusions, clear ownership of traversal.

Lab 3 · Add a preview
5 min
fzf --preview 'sed -n "1,200p" {}' \
  --preview-window='right,60%,wrap' \
  --bind='ctrl-/:toggle-preview'

# Better colorized preview when bat exists
fzf --preview 'bat --color=always --style=numbers --line-range=:300 -- {}' \
  --preview-window='right,60%,border-left'
  • {} becomes shell-quoted current item.
  • -- tells command later arguments are filenames, not flags—use it where tool supports it (bat does; macOS BSD sed does not, hence no -- above).
  • Preview changes as focus moves; keep preview command fast and bounded.
Lab 4 · Open safely after acceptance
4 min
# Shell receives one selected path
file=$(fzf) || exit
${EDITOR:-vi} -- "$file"

# Or replace fzf directly; no parsing round-trip
fzf --bind='enter:become(${EDITOR:-vi} -- {})'
execute vs becomeexecute(...) runs command and returns to fzf. become(...) replaces fzf process; natural for final “open this” action.
Lab 5 · Search inside files, then open exact line
7 min

Here ripgrep searches contents; fzf supplies secondary interactive ranking. Records have path:line:column:text fields.

rg --column --line-number --no-heading --color=always --smart-case . |
  fzf --ansi --delimiter=: \
    --preview 'bat --color=always --highlight-line {2} --line-range=:500 -- {1}' \
    --preview-window='right,60%,+{2}/2' \
    --bind='enter:become(${EDITOR:-vi} +{2} -- {1})'
  • --ansi interprets color codes without matching them.
  • --delimiter=: enables field placeholders.
  • {1} is path; {2} is line number.
  • Preview offset +{2}/2 places matching line near middle.
Lab 6 · Multi-select and inspect output
2 min
printf '%s\n' alpha beta gamma delta | fzf --multi
# Tab marks and moves down; Shift-Tab marks and moves up.
# Enter prints marked rows, or current row when nothing marked.
Tutorial milestoneYou now understand complete pipeline: source records → interactive query → preview current record → accept one/many → use exact emitted value.

4. Practice simulator

Browser approximation builds muscle memory without launching terminal. Type fragments; use /, Tab, and Enter. Scoring intentionally simplified; real fzf ranking is richer.

fzf · practice dataset10/10
↑↓ move · Tab select · Enter accept · Esc clear

5. Query grammar: fuzzy, exact, anchored, negative, OR

Default extended search splits query on unescaped spaces. Terms on same side of | combine with AND; bar separates alternatives. Smart-case is case-insensitive until query contains uppercase.

sbtrkt
Fuzzy. Characters appear in order, not necessarily adjacent.
'wild
Exact substring. Leading apostrophe disables fuzzy matching for term.
'foo'
Exact boundary. Quotes on both ends require word boundaries; underscore also counts as boundary.
^music
Prefix. Line begins with music.
.mp3$
Suffix. Line ends with .mp3.
!remix
Negation. Exclude exact substring match.
!^draft
Negative prefix. Exclude lines beginning with draft.
foo\ bar
Literal space. Backslash escapes term separator.
go$ | rs$
OR. Match suffix go or rs.
extended query anatomyAND + OR + NOT
'api ^src !test .ts$ | .tsx$

meaning: exact “api” AND begins “src” AND NOT “test” AND ends “.ts” OR ends “.tsx”
Shell quoting vs fzf query syntaxWhen supplying initial query with --query, quote for shell first. Example: fzf --query='^src !test .ts$'. Interactive input itself is not shell-expanded.

Scoring and schemes

  • --scheme=default: general scoring.
  • --scheme=path: rewards matches after path separators and prioritizes filename portion.
  • --scheme=history: preserves chronological importance using input order.
  • --tiebreak=CRI[,..]: order for tied scores—length (default), chunk, pathname, begin, end, index; index is implicitly appended last.
  • --exact: exact becomes default; leading apostrophe switches a term back to fuzzy.
  • --no-sort: retain source order while filtering; useful for logs and pre-ranked sources.

Automatic choice: when input comes from terminal (built-in walker or FZF_DEFAULT_COMMAND) and no reload/transform is bound to start, fzf picks path scheme by itself; piped input gets default. Explicit --scheme overrides.

6. Navigation, editing, and selection

Move

/Ctrl K up
/Ctrl J down
PgUp/PgDn pages
Ctrl P/Ctrl N up/down synonyms; with --history they navigate fzf query history instead

Edit query

Ctrl A/Home beginning
Ctrl E/End end
Alt B/Alt F word
Ctrl W delete word
Ctrl U clear before cursor

Select

Enter accept
Tab toggle + move down
Shift Tab toggle + move up
Esc/Ctrl C abort

Preview

Shift ↑/Shift ↓ scroll preview
Custom bind commonly uses Ctrl / to toggle or rotate preview

--multi enables unlimited selection; --multi=3 caps it. If nothing marked, accepting returns current item. Output order follows selected item order as managed by fzf, not necessarily current visual rank—test before relying on ordering.

7. Input/output contract and exit status

StatusMeaningShell implication
0Normal acceptanceUse emitted records
1No match—accepting with zero matches, or --filter/--exit-0 finding nothingOften normal branch
2fzf errorInspect command/options
126/127become permission/command errorCheck executable and permissions
130Interrupted via Esc/Ctrl-CTreat as user cancellation

Capture without losing cancel semantics

# Correct: only act when accepted
if choice=$(printf '%s\n' one two three | fzf); then
  printf 'selected: %s\n' "$choice"
else
  printf 'cancelled\n' >&2
fi

Machine-friendly modes

  • --filter=QUERY: print matching items and exit; useful for tests/scripts, no interactive UI.
  • --select-1: auto-select if initial result has exactly one match.
  • --exit-0: exit if initial result has no match.
  • --print-query: first output record is query, then selections.
  • --expect=ctrl-e,ctrl-v: first output record identifies accept key; remaining records are choices.
  • --read0 --print0: NUL-delimited input/output for arbitrary filenames.
Newline is valid inside Unix filenamesPlain line-based pipelines cannot represent every path. Use NUL-delimited producers/consumers for rigorous filename safety, or constrain domain explicitly.
# GNU/BSD find emits NULs; loop consumes NULs
find . -type f -print0 |
  fzf --read0 --print0 --multi |
  while IFS= read -r -d '' path; do
    printf 'picked: %q\n' "$path"
  done

8. Fields, display transforms, accepted values, placeholders

Structured lines often contain machine ID plus human label. fzf can search/display selected fields while preserving or transforming emitted value.

OptionControlsExample
--delimiter=REGEXHow fields are split--delimiter='\t'
--nth=EXPRFields searched--nth=2..
--with-nth=EXPRFields displayed--with-nth=2,3
--accept-nth=EXPRFields printed on acceptance--accept-nth=1
# input: id TAB name TAB environment
printf '42\tPayments API\tprod\n17\tDocs site\tstaging\n' |
  fzf --delimiter=$'\t' --with-nth=2,3 --accept-nth=1

Field expressions

1 first-1 last2.. from second..-2 through second-last3..5 range.. all

Command placeholders

PlaceholderExpansionUse
{}Current item, shell-quoted--preview 'file -- {}'
{+}All selected items, individually quoted; current if noneexecute(${EDITOR:-vi} -- {+})
{1}, {2..}, {-1}Fields from original recordPath/line parsing
{q}Current query, quoted; accepts field expressions like {q:1}Dynamic ripgrep reload
{n}Zero-based current item index; {+n} for all selectedIndex-aware actions
{f}, {+f}f flag writes values to temporary file, expands to its pathSelections too large for ARG_MAX
Original vs displayed fieldsPreview placeholders extract from original input line, even after --with-nth changes presentation. With both --nth and --with-nth, search field expressions apply to transformed display.

9. Preview windows: context without commitment

Preview command executes whenever focus changes. It receives placeholders and size variables such as $FZF_PREVIEW_LINES and $FZF_PREVIEW_COLUMNS.

fzf --preview 'bat --color=always --style=plain -- {}' \
  --preview-window='right,55%,wrap,border-left' \
  --preview-label=' file preview ' \
  --bind='ctrl-/:change-preview-window(right,55%|down,45%|hidden|)'

Placement

right, left, up, down, or next; add size like 60%.

Behavior

wrap, follow, hidden, cycle, border-left.

Responsive threshold

Alternative layouts can switch below size threshold. Useful for wide terminal vs narrow pane.

Fixed header

~3 keeps top three preview lines fixed while body scrolls.

Preview the right kind of object

# Directory: tree; file: bat
fzf --preview '
  if [ -d {} ]; then
    tree -C -L 2 -- {}
  else
    bat --color=always --style=numbers --line-range=:300 -- {}
  fi
'
Latency budgetFocus event can fire many times per second. Prefer cached metadata, bounded output, and commands that start quickly. Slow preview makes entire interface feel slow.

10. Layout and appearance

Good presentation communicates scope, available actions, and current mode. fzf can run full-screen, occupy terminal height, or open as a floating pane.

ControlPurposeExample
--height=70%Inline finder below cursor; ~ can adapt height to result count--height='~60%'
--popup/--tmuxFloating pane in tmux 3.3+ or Zellij 0.44+--popup=center,80%,60%
--layoutResult direction and prompt position--layout=reverse
--styleCoherent preset--style=full:rounded
Borders/labelsSeparate list, input, header, footer, preview--input-label=' query '
--header/--footerInstructions and action hints--footer='Enter open · Esc cancel'
--ghostHint shown in empty query--ghost='type to filter'
--wrap/--gapLong or multi-line record readability--wrap=word --gap
--freeze-left/rightPin structured fields during horizontal scroll--freeze-left=2
--colorBase scheme and named color overrides--color='dark,hl:#ff9b78'
fzf --height='~70%' --layout=reverse --style='full:rounded' \
  --input-label=' query ' --list-label=' matches ' \
  --preview-label=' preview ' --ghost='type to filter' \
  --footer='Enter accept · Tab select · Esc cancel'

Raw mode and list fidelity

--raw keeps non-matching items visible with a distinct gutter; matching items remain navigable as matches. --no-sort retains input order, while --tac reverses it. These controls help dashboards/logs where surrounding records and source order carry meaning.

Design ruleHeader/footer should explain nonstandard keys and dangerous effects. Labels should name data or mode. Decoration should not conceal selected value or match state.

11. Bindings and actions: turn selector into interface

Binding shape: KEY:ACTION. Separate bindings with comma; chain actions with +. Quote whole argument for shell.

fzf --multi \
  --bind='ctrl-a:select-all,ctrl-d:deselect-all' \
  --bind='ctrl-/:toggle-preview' \
  --bind='alt-enter:execute(${EDITOR:-vi} -- {})' \
  --bind='enter:become(${EDITOR:-vi} -- {})'
Action familyRepresentative actionsIntent
Finishaccept, abort, become(...)Exit or replace process
Selectiontoggle, select-all, deselect-allManage multi-selection
Processexecute(...), execute-silent(...)Run effect, optionally return
Viewtoggle-preview, change-preview-window(...)Change layout/context
Queryclear-query, change-query(...), replace-queryManipulate search
Datareload(...), reload-sync(...)Replace candidate stream
Presentationchange-prompt(...), transform-header(...)Reflect mode/state

Action delimiters

Parentheses are readable: execute(vim {}). If command contains parentheses, choose paired delimiter unlikely to conflict: execute[cmd], execute{cmd}, execute~cmd~. Colon form consumes rest of binding and must be last.

Choose effect semantics deliberately

  • execute(...): visible interactive command; fzf returns afterward.
  • execute-silent(...): no alternate-screen switch; best for quick invisible effect. Still blocks unless command backgrounds itself.
  • become(...): replaces fzf, simplest final launcher, preserves exit behavior better than parsing text through xargs.

12. Dynamic data: reloads, events, transforms

reload(...) swaps candidates while fzf stays open. Events trigger actions from state changes. Together they create live process pickers, search launchers, log viewers, and dashboards.

Manual process refresh

ps -ef |
  fzf --header-lines=1 --layout=reverse --track --id-nth=2 \
    --header='Ctrl-R refreshes · Enter prints process row' \
    --bind='ctrl-r:reload(ps -ef)'

Interactive ripgrep launcher

RG='rg --column --line-number --no-heading --color=always --smart-case'
fzf --ansi --disabled --query='' \
  --bind="start,change:reload:$RG {q} || true" \
  --delimiter=: \
  --preview='bat --color=always --highlight-line {2} -- {1}' \
  --preview-window='right,60%,+{2}/2' \
  --bind='enter:become(${EDITOR:-vi} +{2} -- {1})'
Why --disabled?ripgrep owns content search. fzf passes query to ripgrep through {q} and displays returned records without applying second fuzzy filter. Add key binding to toggle into fzf-only filtering when desired.

Useful events

EventWhenTypical use
startOnce at launchInitial reload or layout state
loadInput stream completesChange “Loading” prompt
changeQuery changesReload search results
result/result-finalFiltering snapshot/final result readyCount-driven state
focusCurrent row changesDynamic labels; keep command fast
one/zeroOne/no matchAuto-accept or fallback reload
every(N)Timer intervalPeriodic refresh, idle UI

reload-sync keeps old list until replacement command completes. --track --id-nth=FIELD helps retain focus across reloads by stable identity.

External control with --listen

fzf can accept actions over local HTTP or Unix domain socket. This enables another process to send up, reload(...), or composed actions while interface runs.

# Terminal 1: local Unix socket (path must end in .sock)
# --no-tmux keeps fzf inline even if your defaults enable --popup/--tmux
fzf --listen=/tmp/my-picker.sock --no-tmux

# Terminal 2: send harmless navigation action
curl --unix-socket /tmp/my-picker.sock http -d 'down'
Control endpoint is privileged UI inputKeep listener local. Set FZF_API_KEY when TCP endpoint needs protection. Safe listener blocks remote-process actions; --listen-unsafe permits them and should not face untrusted clients.

13. Built-in shell integration

Ctrl T

Paste selected file paths into current command line. Configure source with FZF_CTRL_T_COMMAND and UI with FZF_CTRL_T_OPTS.

Ctrl R

Search shell command history. Configure interface with FZF_CTRL_R_OPTS. Recent versions can expose raw/history behavior.

Alt C

Select directory and change into it. Configure source with FZF_ALT_C_COMMAND and UI with FZF_ALT_C_OPTS.

Fuzzy completion

In Bash/Zsh integration, type trigger (default **) then Tab in supported contexts:

vim **<Tab>        # files/directories
cd **<Tab>          # directories
ssh **<Tab>         # hostnames
kill -9 **<Tab>     # processes
export **<Tab>      # variables

Customize before sourcing

# ~/.zshrc, before source <(fzf --zsh)
export FZF_CTRL_T_OPTS="--preview 'bat --color=always --style=numbers -- {}'"
export FZF_ALT_C_OPTS="--preview 'tree -C -L 2 -- {}'"
export FZF_CTRL_R_OPTS="--height=60% --layout=reverse --border"
source <(fzf --zsh)

14. Configuration: defaults without surprising scripts

FZF_DEFAULT_OPTS applies everywhere—interactive commands, scripts, plugins, shell integration. Keep universal defaults visual and conservative. Put workflow behavior in aliases/functions.

# Safe-ish global presentation
export FZF_DEFAULT_OPTS='
  --height=70%
  --layout=reverse
  --border
  --info=inline-right
  --cycle
  --bind=ctrl-/:toggle-preview
'

# Alternative: keep defaults in a file, parsed like FZF_DEFAULT_OPTS
export FZF_DEFAULT_OPTS_FILE="$HOME/.fzfrc"
Avoid global behavior trapsGlobal --multi, --expect, --print-query, --read0, custom Enter, or broad --bind can break consumers expecting ordinary output.

Reusable function pattern

# Zsh/Bash: choose Git branch and switch
fbr() {
  local branch
  branch=$(git for-each-ref --format='%(refname:short)' refs/heads |
    fzf --scheme=path --prompt='branch › ' \
      --preview='git log --color=always --oneline --decorate -20 {}') || return
  git switch -- "$branch"
}

Function owns source, UI, cancel branch, and effect. Easier to test than dense alias; avoids global side effects.

15. Practical recipes

Open one or many files
fzf --multi \
  --preview='bat --color=always --style=numbers --line-range=:300 -- {}' \
  --bind='enter:become(${EDITOR:-vi} -- {+})'
Change directory with preview
cdir() {
  local dir
  dir=$(find . -type d -not -path '*/.git/*' |
    fzf --scheme=path --preview='ls -la -- {}') || return
  cd -- "$dir" || return
}
Switch Git branch
branch=$(git for-each-ref --sort=-committerdate \
  --format='%(refname:short)\t%(committerdate:relative)\t%(subject)' refs/heads |
  fzf --delimiter=$'\t' --with-nth=1,2,3 --accept-nth=1 \
    --preview='git log --color=always --oneline --decorate -30 {1}') || exit
git switch -- "$branch"
Browse Git commits
git log --color=always --format='%C(yellow)%h%Creset %C(cyan)%ad%Creset %s%C(auto)%d' --date=short |
  fzf --ansi --no-sort --scheme=history \
    --preview='git show --color=always {1}' \
    --bind='enter:become(git show --color=always {1} | less -R)'
Search repository text and open line
rg --column --line-number --no-heading --color=always --smart-case . |
  fzf --ansi --delimiter=: \
    --preview='bat --color=always --highlight-line {2} -- {1}' \
    --preview-window='right,60%,+{2}/2' \
    --bind='enter:become(${EDITOR:-vi} +{2} -- {1})'
Choose process, review, then send TERM
pid=$(ps -eo pid,user,%cpu,%mem,command |
  fzf --header-lines=1 --layout=reverse --prompt='process › ' \
    --preview='ps -p {1} -o pid,ppid,user,%cpu,%mem,lstart,command' \
    --accept-nth=1) || exit
kill -TERM "$pid"
Review before signalPrefer TERM; reserve KILL for processes that cannot shut down cleanly. Confirm PID and owner.
Inspect environment variable
name=$(env | cut -d= -f1 | sort | fzf --prompt='env › ') || exit
printf '%s=%s\n' "$name" "${!name}"  # Bash indirect expansion
Pick Docker container
id=$(docker ps --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' |
  fzf --delimiter=$'\t' --with-nth=2.. --accept-nth=1 \
    --preview='docker logs --tail 100 {1} 2>&1') || exit
docker exec -it "$id" sh
Browse recent directories from zoxide
# Function so cd affects your shell and `return` is valid
zd() {
  local dir
  dir=$(zoxide query --list |
    fzf --scheme=path --preview='ls -la -- {}') || return
  cd -- "$dir" || return
}
Use fzf as noninteractive fuzzy filter
printf '%s\n' src/app.ts src/app.test.ts docs/api.md |
  fzf --filter='app !test'
# suitable for assertions; exits 1 when nothing matches

16. Safety, quoting, and portability

Quote shell variables

Use "$choice", never bare $choice. Add -- before path arguments where supported.

Use placeholders

fzf shell-quotes {}/{+}. Do not wrap placeholder in extra quotes unless exact command requires it.

Avoid parsing display text

Carry stable ID as field; show friendly fields; emit ID with --accept-nth.

Treat preview as code

Preview/action templates execute shell. Candidate text can be hostile; placeholder quoting limits injection, not target-command behavior.

Common unsafe shape

# Fragile: word splitting, option injection, cancel ambiguity
rm $(find . -type f | fzf --multi)

# Safer: NUL records + explicit loop + --
find . -type f -print0 | fzf --read0 --print0 --multi |
  while IFS= read -r -d '' path; do
    printf 'would remove: %q\n' "$path"  # review first
  done
Destructive actions deserve confirmationUse fzf to choose, then show exact targets and confirm outside fzf. Avoid binding destructive command directly to a single keystroke.

Portability notes

  • macOS ships BSD tools; GNU-only flags may fail. sed -n, find -print0, and quoted shell loops travel better—but BSD sed rejects -- end-of-options, so omit it there.
  • bat, fd, rg, tree, zoxide are optional companions, not fzf requirements.
  • Action commands run through $SHELL -c on Unix. Script snippets needing Bash should declare Bash.
  • Terminal key encodings vary; some Ctrl/modified bindings cannot be distinguished.

17. Performance and large/streaming inputs

  • Stream early. fzf begins rendering before producer finishes unless --sync.
  • Let specialized search tools search. Use ripgrep for file contents; fzf for interactive narrowing.
  • Bound infinite streams. --tail=100000 --tac --no-sort --exact limits memory and shows newest records first.
  • Keep previews cheap. Limit lines; avoid repeated network/API calls on focus.
  • Avoid unnecessary transforms. --with-nth retains original lines and increases memory use.
  • Choose algorithm only with evidence. Default v2 optimizes ranking; --algo=v1 trades optimal scoring for speed.
# Follow logs, retain latest 100k records
tail -f app.log |
  fzf --tail=100000 --tac --no-sort --exact \
    --preview-window='down,40%,follow' --preview='printf "%s\n" {}'

18. Troubleshooting

SymptomLikely causeFix
Bare fzf lists unexpected filesBuilt-in walker scope/defaultsPipe explicit producer or tune --walker, --walker-root, --walker-skip.
Colors show as escape codesColored input without ANSI parsingAdd --ansi.
Preview breaks on spacesManual interpolation/quotingUse fzf placeholders and --.
Preview painfully slowExpensive focus-triggered commandBound output, cache results, move work to source.
Script acts after EscIgnored exit status/empty substitutionUse choice=$(...) || return or if.
Binding parse errorNested delimiters/commasUse alternate action delimiter; quote complete --bind.
Ctrl T does nothingIntegration not loaded, variable disabled, terminal conflictRun fzf --zsh/--bash check; inspect startup order/key mapping.
Option “unknown”Older package versionCheck fzf --version, fzf --help; update or use older equivalent.
Selected display text corrupts IDPresentation and output conflatedUse delimiter + --with-nth + --accept-nth.
Reload empties list during slow queryAsynchronous reloadUse reload-sync when retaining old list matters.

Debug in layers

  1. Run producer alone. Confirm records and delimiters.
  2. Pipe into plain fzf. Confirm matching/selection.
  3. Add display/field options. Confirm accepted output using | od -c or | sed -n l.
  4. Add preview. Replace command temporarily with printf '%s\n' {}.
  5. Add bindings one at a time.
  6. Add final effect only after inspecting exact emitted value.

19. Exercises: build fluency

Starter · Color picker

Feed 8 color names, select one, print sentence. Handle Esc without printing selection.

One solution
if color=$(printf '%s\n' red orange yellow green blue indigo violet black | fzf); then
  printf 'You chose %s.\n' "$color"
fi

Starter · Exact and negative

Create path list. Query only Markdown files under docs, excluding archive.

Query

^docs !archive .md$

Intermediate · ID/label split

Create tab-separated service ID, name, status. Search name/status, display both, emit only ID.

Options

--delimiter=$'\t' --with-nth=2,3 --accept-nth=1

Intermediate · Git branch switcher

Sort by commit date, preview 20 commits, return on Esc, switch accepted branch.

Advanced · Dual-mode search

Start with ripgrep owning query; bind key to stop reload and enable fzf search; update prompt to show current mode.

Advanced · Live process board

Refresh every 2 seconds, retain cursor by PID, preview details, require explicit confirmation before signal.

Completion testExplain each workflow aloud as: source record → searchable/displayed fields → preview placeholders → accepted record → effect → cancel behavior. If each part is explicit, design is probably sound.

20. Retrieval quiz

Answer aloud before revealing. Mark honestly; score persists locally in this browser.

Score: 0 learned · 0 review · 0 answered
Q1Query for entries starting with src, excluding test, ending .ts or .tsx?
^src !test .ts$ | .tsx$ — terms AND within each side of the bar; bar separates OR alternatives.
Q2Difference between execute(...) and become(...)?
execute runs the command and returns to fzf. become replaces the fzf process—natural for a final “open this” action.
Q3Meaning of exit statuses 0, 1, 2, 130?
0 normal acceptance · 1 no match · 2 fzf error · 130 cancelled with Esc/Ctrl-C. (126/127: become permission/command errors.)
Q4Tab-separated id/name/status rows: show name+status, emit only id—options?
--delimiter=$'\t' --with-nth=2,3 --accept-nth=1.
Q5What do {}, {+}, and {q} expand to?
{} current item, shell-quoted. {+} all selected items (current if none marked). {q} current query.
Q6Why does the interactive ripgrep launcher use --disabled?
ripgrep owns content search; fzf passes {q} to it via reload on change and must not apply a second fuzzy filter over the results.
Q7Filename-safe multi-select pipeline for arbitrary paths?
find . -type f -print0 | fzf --read0 --print0 --multi | while IFS= read -r -d '' path; … — NUL delimiters end-to-end, review before destructive action.
Q8After --with-nth transforms display, which line do preview placeholders read? Which do --nth expressions read?
Preview placeholders extract from the original input line. --nth search expressions apply to the transformed display line.
Q9Keep the old list visible during a slow reload, and keep focus on the same logical item—how?
reload-sync(...) retains the old list until the new stream completes; --track --id-nth=FIELD retains focus by stable identity.
Q10What does 'foo' (quotes on both ends) match?
Exact occurrences of foo with both ends at word boundaries; underscore also counts as a boundary (unlike regex \b).
Q11Bound memory while following an endless log stream?
--tail=N caps items kept in memory; combine with --tac --no-sort to show newest first in source order.
Q12Effect of setting FZF_CTRL_T_COMMAND to an empty string before sourcing integration?
Disables the Ctrl+T binding entirely (same pattern for FZF_ALT_C_COMMAND).

21. One-page cheat sheet

Queries

abc fuzzy
'abc exact
^abc prefix
abc$ suffix
!abc exclude
a | b OR
a b AND

Core UI

Ctrl J/K move
Enter accept
Tab mark + down
Shift Tab mark + up
Esc cancel
Shift ↑/↓ preview scroll

Fields

-d delimiter
--nth search
--with-nth display
--accept-nth output
{1} field
{+} selections
{q} query

Build UI

--preview context
--bind behavior
execute run + return
become replace
reload new data
--disabled selector mode

Safe shell

Capture status
Quote "$value"
Use --
NUL: --read0 --print0
Separate display from ID

Learn installed build

fzf --version
fzf --help
fzf --man
man fzf

# Universal starting pattern
choice=$(producer | fzf --prompt='choose › ' --preview='inspect {}') || exit
consumer -- "$choice"

22. Sources and version note

This guide prioritizes installed fzf 0.74.2 help/man page, then official project documentation. Features differ across releases; use local fzf --man as authority for installed binary.

Guide examples target POSIX-ish shell where practical and label Bash/Zsh assumptions. Optional tools: ripgrep (rg), bat, fd, tree, zoxide, Git, Docker.