Ramblings of an aging IT geek
← Ramblings of an aging IT geek
tooling

the day fzf quietly took over my shell

How a single fuzzy finder rewired the way I move through files, history, processes and git, and the small set of bindings and functions that did most of the work.

A mechanical keyboard lit by a terminal window

There is a particular kind of tool that you install, ignore for a week, then suddenly cannot remember how you worked without. fzf was that for me. It is a fuzzy finder: you pipe it a list of lines, it gives you an interactive filter, and it hands the chosen line back on stdout. That is the whole idea. The whole reason it is worth a post is everything you can hang off that idea.

I had been a Ctrl-R user for years, in the resigned way you tolerate a creaky stair. Reverse-search worked, mostly, as long as I remembered the exact prefix of the command I wanted and never typo'd it. The moment fzf took over Ctrl-R I realised how much friction I had been ignoring. I can type three letters from anywhere in the command, in any order, and watch the matches narrow live. The command I ran last Tuesday that started with kubectl and had --context somewhere in the middle is now four keystrokes away instead of a guessing game.

So I sat down one wet Sunday and went looking for everywhere else the same shape of problem turned up. There were a lot of them.

the three bindings that earn their keep

Install the shell integration and you get three default key bindings, and honestly these alone are worth the entry fee.

Ctrl-R is history, as above. Ctrl-T drops a fuzzy file picker into the current command line, so I type vim then Ctrl-T and pick the file instead of tab-completing my way down a directory tree like it is 1999. Alt-C does the same for directories but cds straight into the one I choose. That last one rewired my muscle memory hardest. I used to keep a mental cache of cd ../../.. incantations. Now I just fuzzy-jump.

The trick that makes file picking actually pleasant is the preview window. Point FZF_DEFAULT_OPTS at something that can render the highlighted line and you get a live preview as you move:

export FZF_DEFAULT_OPTS="--height 40% --layout=reverse --border --preview 'bat --color=always --style=numbers {} 2>/dev/null || cat {}'"
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"

I lean on fd rather than find for the default command because it respects .gitignore and is quick, but plain find works if you would rather not add another binary. The preview uses bat when it is there and falls back to cat, because half the appeal of a tool like this is that it degrades gracefully on a box you have just SSH'd into.

A code editor and terminal side by side mid-search

the functions I actually wrote myself

The defaults are the gateway. The habit-forming part is wrapping fzf around the things you do twenty times a day. None of these are clever. That is the point.

Killing a process by name without remembering its PID:

fkill() {
  local pid
  pid=$(ps -ef | sed 1d | fzf -m | awk '{print $2}')
  [ -n "$pid" ] && echo "$pid" | xargs kill -"${1:-9}"
}

Checking out a git branch when I have forgotten whether it is feature/thing or feat/the-thing:

fco() {
  local branch
  branch=$(git branch --all | grep -v HEAD | sed 's/^[* ]*//;s#remotes/[^/]*/##' \
    | sort -u | fzf) || return
  git checkout "$branch"
}

And the one I did not expect to love, browsing git history with a live diff preview, so I can find the commit that broke a thing by reading the diffs rather than squinting at one-line subjects:

flog() {
  git log --color=always --format="%C(auto)%h %s %C(black)%C(bold)%cr" "$@" \
    | fzf --ansi --no-sort --preview 'git show --color=always {1}'
}

The shared move in all three is the same: produce lines, filter them interactively, pull a field out with awk or sed, do something with it. Once you have that pattern in your hands you start seeing candidates everywhere. Selecting a Docker container to attach to. Picking an SSH host out of your ~/.ssh/config. Choosing which of forty stale branches to delete. Each one is a five-line function and each one removes a small daily annoyance you had stopped noticing.

Terminal output with a fuzzy filter narrowing a long list

what actually changed

The honest summary is that fzf did not make me faster at any one task in a way I could put a stopwatch to. What it changed was the cost of recall. I no longer keep a working set of exact filenames, branch names, container IDs and command histories in my head, ready to type perfectly first time. I keep a fuzzy impression and let the tool close the gap. That is a smaller cognitive load than it sounds, spread across a day, and the effect compounds.

It also made me braver about long histories and big trees. When finding a thing is cheap, you stop pruning your environment to keep the find cheap. I let my shell history grow without bound now. I keep deep directory hierarchies that I would once have flattened. The finder absorbs the mess.

If you try one thing from this, make it the three default bindings, lived with for a fortnight. Do not write a single function until the muscle memory for Ctrl-R, Ctrl-T and Alt-C is in your fingers. The custom wrappers are the fun part, but they are also the part you will tinker with forever and never quite finish. The bindings are the part that quietly changes how you work, and they were there the moment you installed it.