TechQuant Blog

fzf で端末作業が速くなる — 毎日叩く実戦パターン集

7分で読める

土曜の夕方、git のブランチが 47 本溜まっていた。うち 30 本は半年触っていない。git branch | grep で絞ろうとしたが、そもそもブランチ名を正確に覚えていない。ここで fzf を噛ませたら、キーワードの断片を打ち込むだけで 2 秒で目的のブランチに飛べた。Ctrl+R の履歴検索しか使っていなかった過去の自分を殴りたくなった。

fzf は「インストールしたまま Ctrl+R 専用機になっている」人が一番多いツールだと思う。もったいない。シェル関数と組み合わせた瞬間、端末の移動速度が体感で 3 倍になる。この記事は、自分が Raspberry Pi 5 と MacBook 両方で毎日叩いている fzf のパターンを、使用頻度の高い順に並べたメモだ。

インストールは apt install fzfbrew install fzf。git 経由で入れると ~/.fzf.bash のキーバインドが自動で有効になる。この記事はバージョン 0.48 以降を想定している。

Ctrl+R の先にある世界

fzf を入れて一番最初に気付くのは Ctrl+R が別物になることだ。履歴が全文検索できる。ここで止まる人が多い。でも fzf の本体は「標準入力を受けて、選んだ行を標準出力に吐く」というだけのシンプルなフィルタだ。これに気付いてから、自分のシェル設定が一気に変わった。

たとえばこれだけで、カレントディレクトリ配下から選んだファイルを開ける。

vim "$(find . -type f | fzf)"

ちなみに find の代わりに fdrg --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 という関数で登録している。zzoxide と役割が被ると思われがちだが、微妙に違う。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 を使ってインタラクティブモードをスキップするか、そもそも awkjq で書き直したほうが速い。以前書いたcron から systemd timer への移行の記事でも触れたが、自動化に混ぜていいツールと混ぜちゃいけないツールの線引きは意外と大事だ。

結論 — fzf は「使い方を足し算する」ツール

fzf は単独では大したことをしない。真価は「他のコマンドの出力をフィルタできる」ところにある。gitpslssshdocker、なんでもいい。標準出力を吐くコマンドなら全部 fzf を噛ませられる。

一度に覚える必要はない。自分は最初の 1 ヶ月は Ctrl+R だけ、次に fbr を足し、半年後に --preview の世界に入った。段階的に積み上げるほうが結局身につく。

毎日触る端末が 30 秒速くなると、1 年で 3 時間以上浮く。読者が明日から fbr を一つでもエイリアスに追加できれば、この記事の仕事は終わりだ。