Skip to main content

fzf Philosophy and Design

fzf's power comes from its philosophy: do one thing, do it interactively, and compose with everything else. Understanding this design principle makes you a far more effective fzf user.

The Unix Filter Model

Unix tools are built around the idea of small, composable programs connected by pipes. fzf is a perfect Unix citizen:

STDIN → fzf (interactive select) → STDOUT

It has no opinion about:

  • What data it receives (files, processes, commits, URLs — anything)
  • What the caller does with the output (open, delete, copy, pipe further)
  • What shell or editor you use

This is intentional. By staying minimal, fzf works everywhere.

"Interactive" vs "Batch" Filtering

ModeToolUse case
Batchgrep, awk, sedYou know the pattern; automate it
InteractivefzfYou need to see the options and choose
# Batch: you know exactly what you want
grep "ERROR" /var/log/syslog | tail -20

# Interactive: you want to browse and pick
grep "ERROR" /var/log/syslog | fzf --preview 'echo {}' --preview-window=down:3

The Three Rules of fzf Design

Rule 1: Accept Any Line-Based Input

fzf does not care if the input is filenames, log lines, JSON, git SHAs, IP addresses, or hostnames. It treats everything as opaque text lines and lets you filter them.

# Works with any list:
cat /etc/passwd | fzf # users
docker images | fzf # images
kubectl get pods | fzf # pods
history | fzf # commands
curl -s 'https://api.github.com/repos/torvalds/linux/commits' \
| jq -r '.[].commit.message' | fzf # commit messages

Rule 2: Output is Consumable by Other Tools

fzf outputs what you select to stdout. The consuming command decides what to do with it:

# Select, then open
fzf | xargs nvim

# Select, then delete
find . -name "*.tmp" | fzf -m | xargs rm

# Select, then copy to clipboard
ls | fzf | xclip -selection clipboard

Rule 3: Behavior is Composable via Flags

fzf's extensive --flag system lets you compose complex behaviors at the call site, not inside fzf itself:

fzf \
--multi \ # allow multiple selections
--preview 'bat --color=always {}' \ # preview command
--preview-window 'right:60%' \ # preview layout
--bind 'ctrl-/:toggle-preview' \ # custom keybinding
--header 'Select files to edit' \ # status header
--prompt 'Files> ' \ # custom prompt
--pointer '▶' \ # selection pointer
--marker '✓' # multi-select marker

What fzf Deliberately Omits

FeatureWhy fzf omits it
Built-in file browserUse find, fd, or ls as input — more flexible
Config fileUse FZF_DEFAULT_OPTS env variable — scriptable
Plugin systemUse shell functions — composable with existing tools
State persistenceUse the shell's history and aliases
GUI versionIt's a TUI tool — intentionally terminal-native

The Mental Model: "Interactive xargs"

If you know xargs, you can think of fzf as interactive xargs:

# xargs: non-interactive, process all matching items
find . -name "*.log" | xargs grep "ERROR"

# fzf + xargs: interactive, YOU pick which log to grep
find . -name "*.log" | fzf | xargs grep "ERROR"

Composability in Practice

flowchart TD
A["Any command\nthat outputs lines"] --> FZF["fzf\n(you pick)"]
FZF --> B["Any command\nthat accepts lines"]

subgraph Sources [Example Sources]
S1["find / fd"]
S2["git log"]
S3["ps aux"]
S4["docker ps"]
S5["history"]
S6["cat ~/.ssh/config"]
end

subgraph Consumers [Example Consumers]
C1["nvim / vim"]
C2["git checkout"]
C3["kill"]
C4["docker exec"]
C5["ssh"]
C6["xargs rm"]
end

Next Steps