I'll put the conclusion first: installing fzf changed how I use the terminal more than any other tool in the last few years, and it did it by quietly deleting steps I didn't know I was performing. I no longer remember directory paths. I no longer carefully craft find incantations. I type a few letters of what I half-remember and the right thing is selected. That's the whole pitch, and it took me about a week to stop noticing it was there because it had become how the shell works.
If you haven't met it, fzf is a general-purpose fuzzy finder. You pipe it a list of lines on stdin, it gives you an interactive filter, and it prints the line you chose on stdout. That's the entire program. Everything below is just clever uses of that one idea.
the three default bindings
Out of the box, the shell integration gives you three things, and these alone are worth the install.
Ctrl-R replaces your shell's reverse history search. The native one is fine until you can't quite remember the command, at which point it's useless because it needs a contiguous substring. fzf lets me type docker prune and find docker system prune -af --volumes from three weeks ago even though those words aren't adjacent. I run this dozens of times a day.
Ctrl-T pastes a file path into the current command line, chosen fuzzily from everything under the current directory. So I type vim then Ctrl-T, filter to the file, hit enter, and the path is just there. No ls, no tab-cycling through twelve similar names.
Alt-C does the same but cds into a chosen subdirectory. I navigate deep trees by typing fragments of the destination rather than walking there one cd at a time.
the part that actually rewired things
The defaults are nice. The custom shell functions are where it stops being a utility and starts being infrastructure. The pattern is always the same: produce a list, pipe it through fzf, do something with the selection. Once that clicks you start seeing it everywhere.
Checking out a git branch when you can only remember half its name:
fbr() {
local branch
branch=$(git branch --all | grep -v HEAD | sed 's/.* //' | fzf) || return
git checkout "${branch#remotes/origin/}"
}
Killing a process without first running ps, copying a PID, and pasting it:
fkill() {
local pid
pid=$(ps -ef | sed 1d | fzf -m | awk '{print $2}') || return
echo "$pid" | xargs kill "-${1:-9}"
}
The -m there enables multi-select, so I can tag several processes with Tab and kill them all at once. That detail matters: the difference between selecting one thing and selecting a set is the difference between a search box and a batch tool.
My most-used one is a fuzzy git log browser. It lists commits, and the preview window on the right shows the diff of whichever commit the cursor is on:
fshow() {
git log --color=always \
--format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
fzf --ansi --no-sort --reverse \
--preview 'git show --color=always {1}'
}
That preview window is the second thing that genuinely changed my workflow, after fuzzy matching itself. fzf will run an arbitrary command for the highlighted line and show its output beside the list. So you're not just picking from names, you're picking while looking at the contents. Browsing files with the file shown, browsing commits with the diff shown, browsing branches with their recent log shown. The selection and the inspection happen in one motion.
what changed in my head
The mechanical wins are obvious enough: fewer keystrokes, less remembering, less breaking flow to go and look something up. But the deeper change is that I stopped maintaining a mental map of exact names and paths. I used to half-consciously memorise where things lived and what commands were called. Now I keep a vague impression and let the finder close the gap. That sounds lazy, and it is, but it's freed up the attention I was spending on bookkeeping for the actual problem in front of me.
It also turned "list, then pick" into a reflex. Any time I'm about to copy a value out of one command to paste into another, there's now a little voice asking whether that should just be an fzf function. Often it should. The list of small tools in my dotfiles has grown steadily and each one removes a paper cut I'd stopped noticing.
If you take one thing from this: install it, live with the three default bindings for a week, and don't try to learn the rest until they start to itch. The custom functions will suggest themselves once the core habit is in your fingers. Mine certainly did, and I can't imagine going back to remembering paths like an animal.