The fzf shell setup that finally stuck
fzf is one of those tools I’ve installed six times over the years and never internalized. The problem wasn’t fzf. The problem was my shell integration — out of the box, I’d get Ctrl-R for history search and Ctrl-T for file completion, and I’d use them for a week, and then forget. The defaults aren’t quite what I want, and “quite” compounds into “never.”
This year I sat down and configured it properly. Two weeks later I can’t live without it. Sharing the config in case it’s useful.
The baseline install
# mac
brew install fzf
$(brew --prefix)/opt/fzf/install
# or just the binary
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
The install script sets up shell integration. The bit I cared about: enable keybindings, skip everything else.
The actual config
In my .zshrc (same works in bash with minor changes):
export FZF_DEFAULT_COMMAND='rg --files --hidden --follow --glob "!.git/*"'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_DEFAULT_OPTS="
--height=60%
--layout=reverse
--border
--bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)'
--bind 'ctrl-/:toggle-preview'
"
export FZF_CTRL_T_OPTS="
--preview 'bat --color=always --style=plain --line-range :300 {}'
--preview-window 'right:60%'
"
export FZF_CTRL_R_OPTS="
--preview 'echo {}'
--preview-window 'down:3:hidden:wrap'
--bind '?:toggle-preview'
"
export FZF_ALT_C_COMMAND='fd --type d --hidden --follow --exclude .git'
export FZF_ALT_C_OPTS="
--preview 'tree -C {} | head -200'
"
Translation, in order of importance to me:
rg --filesinstead of find. Faster, respects gitignore, sensible defaults. Huge difference on large repos.batfor file preview. Syntax highlighted.--line-range :300stops huge files from eating performance.- 60% height, reverse layout. Takes less of my screen, prompt at the top where my eyes go.
ctrl-yto copy selection. Often I just want the filename in the clipboard.pbcopyis Mac-specific; on Linux usexcliporwl-copy.ctrl-/to toggle preview. Sometimes the preview is noise; I want it gone.
The git integration that changed everything
Pure fzf is useful. fzf + git is transformative. A few functions I use constantly:
# interactive git branch switcher
fbr() {
local branches branch
branches=$(git for-each-ref --count=30 --sort=-committerdate refs/heads --format="%(refname:short)") &&
branch=$(echo "$branches" | fzf --preview 'git log --oneline --graph --color=always {} | head -50') &&
git switch "$branch"
}
# interactive git log with diff preview
fgl() {
git log --graph --color=always --format="%C(auto)%h %s %C(blue)%an %C(cyan)%ar" "$@" |
fzf --ansi --no-sort --tiebreak=index --preview \
'f() { set -- $(grep -o "[a-f0-9]\{7,\}" <<< "$1" | head -1); [ "$1" ] && git show --color=always "$1"; }; f {}' \
--bind 'enter:execute:(grep -o "[a-f0-9]\{7,\}" <<< {} | head -1 | xargs -I{} git show --color=always {}) | less -R'
}
# interactive stash viewer
fstash() {
local out sha
out=$(git stash list --pretty=format:'%gd: %gs' | fzf --preview 'git stash show -p $(echo {} | cut -d: -f1) --color=always') || return
sha=$(echo "$out" | cut -d: -f1)
git stash pop "$sha"
}
fbr for “switch to the branch I was just on,” fgl for “browse git log and show the diff of whatever I pick,” fstash for “bring back that stash I forgot about.”
The thing that made it stick
The thing I did that I hadn’t done before: I deleted my old aliases. I used to have gco for git checkout, gb for git branch, etc. Convenient — too convenient. Every time I reached for them I was skipping fzf. I replaced them with the fzf functions. Now gco is fbr, gb is fbr. The old way isn’t an option. Two weeks later I couldn’t imagine using git branch to look at my branches.
This pattern generalizes: if you want to build muscle memory for a new tool, remove the alternative. Don’t just add the new option.
What I still don’t use fzf for
- File opening in my editor. nvim’s telescope fills that slot and integrates better with the editor.
- Process picker. I tried, didn’t stick.
ps aux | grepworks fine. - ssh hostname picker. Always liked
tabcompletion for this. Your mileage may vary.
Reflection
I’m a big believer in keyboard tools but I’m also lazy. A tool has to pay its cost in learning with big usability gains or I’ll drift back to whatever I was doing. fzf eventually paid off because I gave myself no alternative. And because I tuned the preview/layout to my aesthetic. The out-of-the-box experience is too plain to excite me; the tuned version feels like a spaceship.
If fzf hasn’t stuck for you, try the remove-the-alternative trick. Delete your gco alias. See what happens.
Related: git worktree as a daily driver.