fzf で端末作業が速くなる — 毎日叩く実戦パターン集
土曜の夕方、git のブランチが 47 本溜まっていた。うち 30 本は半年触っていない。git branch | grep で絞ろうとしたが、そもそもブランチ名を正確に覚えていない。ここで fzf を噛ませたら、キーワードの断片を打ち込むだけで 2 秒で目的のブランチに飛べた。Ctrl+R の履歴検索しか使っていなかった過去の自分を殴りたくなった。
fzf は「インストールしたまま Ctrl+R 専用機になっている」人が一番多いツールだと思う。もったいない。シェル関数と組み合わせた瞬間、端末の移動速度が体感で 3 倍になる。この記事は、自分が Raspberry Pi 5 と MacBook 両方で毎日叩いている fzf のパターンを、使用頻度の高い順に並べたメモだ。
インストールはapt install fzfかbrew install fzf。git 経由で入れると~/.fzf.bashのキーバインドが自動で有効になる。この記事はバージョン 0.48 以降を想定している。
Ctrl+R の先にある世界
fzf を入れて一番最初に気付くのは Ctrl+R が別物になることだ。履歴が全文検索できる。ここで止まる人が多い。でも fzf の本体は「標準入力を受けて、選んだ行を標準出力に吐く」というだけのシンプルなフィルタだ。これに気付いてから、自分のシェル設定が一気に変わった。
たとえばこれだけで、カレントディレクトリ配下から選んだファイルを開ける。
vim "$(find . -type f | fzf)"
ちなみに find の代わりに fd や rg --files を使うともっと速い。以前書いたripgrep の実践テクニックと組み合わせるのが自分の定番だ。
vim "$(rg --files | fzf)"
.gitignore を尊重してくれるので、node_modules やら .venv やらが混ざらない。地味に効く。
git 周りが劇的に速くなる
ブランチ切り替え
冒頭で書いたブランチ問題。これを毎日快適にするための関数がこれ。
fbr() {
local branches branch
branches=$(git branch --all | grep -v HEAD) &&
branch=$(echo "$branches" | fzf -d $'\t' +m) &&
git checkout $(echo "$branch" | sed 's/.* //' | sed 's#remotes/[^/]*/##')
}
fbr と叩けばインタラクティブにブランチを選べる。リモートブランチも混ざる。検索は fuzzy なので、fx-log というブランチを探すのに flog とだけ打てば大抵ヒットする。
コミット選択(git log をインタラクティブに)
fshow() {
git log --graph --color=always \
--format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
fzf --ansi --no-sort --reverse --tiebreak=index \
--preview 'echo {} | grep -o "[a-f0-9]\{7,\}" | head -1 |
xargs -I % git show --color=always %'
}
右側にコミットの diff がプレビュー表示される。「あのコミットで何変えたっけ」を 3 秒で確認したいときに刺さる。--preview オプションは fzf の魂みたいな機能で、これを使えるかどうかで生活の質が変わる。
ステージングも fzf で
fadd() {
local files
files=$(git status -s | fzf -m --ansi \
--preview 'echo {} | awk "{print \$2}" | xargs git diff --color') &&
echo "$files" | awk '{print $2}' | xargs git add
}
Tab キーで複数選択できる(-m オプション)。変更ファイル一覧から必要なものだけ選んで add する。git add -p より速いケースが多い。
プロセスと ssh をワンタッチで
暴走プロセスを殺す
fkill() {
local pid
pid=$(ps -ef | sed 1d | fzf -m | awk '{print $2}')
if [ "x$pid" != "x" ]; then
echo "$pid" | xargs kill -${1:-9}
fi
}
fkill と打って、プロセス名の断片を入力、Tab で複数選択、Enter で一括 kill。Chrome のタブが 50 個暴れて OOM 予備軍になったときに何度も救われた。
ssh の接続先を選ぶ
fssh() {
local host
host=$(grep -i '^host ' ~/.ssh/config | grep -v '*' | awk '{print $2}' | fzf)
[ -n "$host" ] && ssh "$host"
}
自分の ~/.ssh/config は 20 ホストくらい登録されていて、Raspberry Pi が 3 台、VPS が 2 台、AWS EC2 が…と覚えていられない。fssh なら pi と打つだけで該当ホストに絞り込める。ちなみにホスト側のマシンを格安で立てるならお名前.comの高性能VPS
あたりが月数百円で使えるので、練習環境として悪くない。
プレビューを使いこなす
fzf の真価は --preview にある。選択中の項目に対して任意のコマンドを叩いて、右ペインに結果を出せる。これを覚えると、自作ツールが一段レベルアップする。
rg --files | fzf --preview 'bat --color=always --line-range :50 {}'
ファイルを選ぶときに、冒頭 50 行を bat でシンタックスハイライトしながら表示する。目的のファイルを探すのが一気に楽になる。bat が入っていなければ cat でも十分だ。
ディレクトリ版はこう。
cd "$(fd -t d | fzf --preview 'ls -la {}')"
自分はこれを fcd という関数で登録している。z や zoxide と役割が被ると思われがちだが、微妙に違う。z は「行き先を記憶している」のに対し、fzf + fd は「今のディレクトリツリー全体から絞り込む」。プロジェクトに初めて入ったときは後者が強い。
意外と知られていないオプション
--height で画面を埋めない
export FZF_DEFAULT_OPTS="--height 40% --reverse --border"
デフォルトだと画面全体が fzf に乗っ取られる。--height 40% を指定しておくと、下 40% だけ使って画面を切り替えない。作業の流れが途切れにくい。--reverse で入力欄が上に来るのも地味に効く。
--bind で任意のキーを生やす
fzf --bind 'ctrl-e:execute(vim {})'
選択肢の上で Ctrl+E を押すと、その項目を vim で開く。自分は Ctrl+D に「プレビューを全画面化」、Ctrl+Y に「選んだパスをクリップボードにコピー」を割り当てている。--bind は文字列で全部書けるので、bashrc にそのまま詰め込める。
tiebreak でスコアの挙動を変える
検索結果の並びが直感と違うとき、--tiebreak を触ると解決することが多い。
fzf --tiebreak=begin,length,index
前方一致を優先し、次に短いものを優先、最後に入力順。自分のコマンド履歴検索はこの設定に落ち着いた。
現場でハマったポイント
ここは正直まだ試行錯誤中なのだが、いくつか書いておく。
ひとつ目。fzf のプレビューで git diff を叩くと、巨大リポジトリだと 1 秒くらいカクつく。--preview-window=hidden でいったん非表示にして、ショートカットキーで必要なときだけ出す運用が落ち着いた。
ふたつ目。シェル関数で fzf を呼ぶときに、$(...) でラップすると改行や空白が含まれるパスが事故を起こす。while IFS= read -r line で受ける形が一番安全。面倒だが、一度スクリプトが壊れて原因特定に 30 分使った経験があるので、今は基本こっちで書く。
みっつ目。JSON を整形して fzf に流したいことがあるが、ここは jq との合わせ技になる。この辺りはjq の実践パターンでも書いた --arg の使い方と組み合わせると、API レスポンスから特定の ID を選ぶような UI が 5 行で書ける。
CI や自動化スクリプトと fzf の境界線
fzf はインタラクティブなので、非対話シェル(cron や systemd timer の中)では動かない。当たり前なのだが、最初のうちはこれを忘れてスクリプトが固まる。自動化したいなら --filter を使ってインタラクティブモードをスキップするか、そもそも awk や jq で書き直したほうが速い。以前書いたcron から systemd timer への移行の記事でも触れたが、自動化に混ぜていいツールと混ぜちゃいけないツールの線引きは意外と大事だ。
結論 — fzf は「使い方を足し算する」ツール
fzf は単独では大したことをしない。真価は「他のコマンドの出力をフィルタできる」ところにある。git、ps、ls、ssh、docker、なんでもいい。標準出力を吐くコマンドなら全部 fzf を噛ませられる。
一度に覚える必要はない。自分は最初の 1 ヶ月は Ctrl+R だけ、次に fbr を足し、半年後に --preview の世界に入った。段階的に積み上げるほうが結局身につく。
毎日触る端末が 30 秒速くなると、1 年で 3 時間以上浮く。読者が明日から fbr を一つでもエイリアスに追加できれば、この記事の仕事は終わりだ。