fzfを活用してTerminalの作業効率を高める

  • 20
    Like
  • 1
    Comment

はじめに

今回は作業効率化をすすめるにあたって有用なfzfの利用例を紹介したいと思います。
似たようなものでpecoというのもありますが、fzfの記事が少ないと思うので、今回はfzfについて書いていきたいと思います!

(あとfzfはVimでも使えるようにサポートされているので、Vimmerの方はpecoよりもfzfかなということもあり…。)

fzfとは

fzf
https://github.com/junegunn/fzf

fzfとはCLIでインクリメンタルに曖昧な検索が可能になるGO言語製のツールです。
標準出力をパイプでfzfコマンドで渡すだけで、標準出力の内容を対象に検索できます。

$ find . | fzf 

スクリーンショット 2017-10-19 22.02.46.png

上部の入力箇所でインクリメンタル曖昧検索しながら、(CLIとしては慣れ親しんだキーバインドの)Ctrl-n,Ctrl-pで下部のリストから選択することができます。
(もちろんカーソルキーでもOK)

最初は『検索できるだけで、何が便利やねん』という感想を持たれた方も多いかと思いますが、CLIの環境において選択的インターフェースの提供は、工夫次第でかなり強力になります。

まずはfzfをインストールしましょう

fzfのインストール記事は他にいくらでもあるので割愛します。
たぶんこちらとかの記事を参考にすると良さそうです

ちなみに私の設定っぽい設定は今のところこれだけです。(←zsh)

.zshrc
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"'
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'

fzfでヒストリー検索

さて、fzfをちゃんとセットアップできたら、まずはコマンドヒストリーの曖昧インクリメンタル検索を使ってみましょう。

デフォルトのヒストリー検索よりも、一覧を見ながらかつ柔軟に検索できるようになるので、一度使い始めるとデフォルトのコマンドヒストリー検索には戻れません。

スクリーンショット 2017-10-19 22.45.13.png

参考:本家README.md
https://github.com/junegunn/fzf#key-bindings-for-command-line

fzfで実装する便利関数

さて早速、作業効率化を進めていきましょう。
まず何をすればよいかわからない方は、サンプルの宝庫である本家Wikiを見るのがおすすめです。
https://github.com/junegunn/fzf/wiki

ここではいくつか本家Wikiにある便利関数を紹介します。

fbr

下記のような記述を.bashrcや.zshrcなどに書きます。

# fbr - checkout git branch
fbr() {
  local branches branch
  branches=$(git branch -vv) &&
  branch=$(echo "$branches" | fzf +m) &&
  git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //")
}

fbrコマンドで今ローカルに存在するbranchを選択して切り替えられるようになります。

スクリーンショット 2017-10-19 22.13.42.png
※サンプルはvimのaleのgitリポジトリが丁度良さそうだったので勝手に使わせていただいています(私自身は全く貢献してません…)

※ 本家Wikiに書いてあるサンプルは基本的にfzf+(git) branch = fbrみたいなネーミングのノリです。

さぁ、どんどん行きましょう。

fbr

またしてもfbrという名前がついているのですが、今度はリモートブランチを含めた検索です。

fbr - checkout git branch (including remote branches)
fbr() {
  local branches branch
  branches=$(git branch --all | grep -v HEAD) &&
  branch=$(echo "$branches" |
           fzf-tmux -d $(( 2 + $(wc -l <<< "$branches") )) +m) &&
  git checkout $(echo "$branch" | sed "s/.* //" | sed "s#remotes/[^/]*/##")
}

スクリーンショット 2017-10-19 22.21.24.png

個人的にはfbrm(fzf+branch+remote)って関数名にしてfbrとは分けて使ってます。

fshow

# fshow - git commit browser
fshow() {
  git log --graph --color=always \
      --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
  fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
      --bind "ctrl-m:execute:
                (grep -o '[a-f0-9]\{7\}' | head -1 |
                xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
                {}
FZF-EOF"
}

スクリーンショット 2017-10-19 22.33.03.png

git log の--graphオプションもfzfに突っ込んでしまえば、立派なインターフェースの出来上がりです。
Enterでgit showの状態になります。

fd

もうちょっと簡単な例を見てどうやってカスタマイズしていけばいいか理解を深めてみましょう。

fdはfindコマンドで下層までをリスト化し、標準出力をパイプでfzfに渡し、最後にcdコマンドに結果を渡しているだけということがわかります。

# fd - cd to selected directory
fd() {
  local dir
  dir=$(find ${1:-.} -path '*/\.*' -prune \
                  -o -type d -print 2> /dev/null | fzf +m) &&
  cd "$dir"
}

スクリーンショット 2017-10-19 23.04.50.png
(※個人的にfcdって名前にしてみましたがあんまり使ってません…。)

なるほど、これなら自分でも実装できそうです。

自分が使っているfzfの例

git worktreeコマンドは皆さんお使いでしょうか。僕も最近使い始めて見たのですが、マルチタスクな日は便利すぎてかなりお世話になっています。
参考: ブランチの切り替えをしなくてよくなるコマンド git worktree がすごい!

でも、worktreeで作成したブランチまで移動するのがだるすぎる…。
ということでfzfを使って作業効率化してみます。

# worktree移動
function cdworktree() {
    # カレントディレクトリがGitリポジトリ上かどうか
    git rev-parse &>/dev/null
    if [ $? -ne 0 ]; then
        echo fatal: Not a git repository.
        return
    fi

    local selectedWorkTreeDir=`git worktree list | fzf | awk '{print $1}'`

    if [ "$selectedWorkTreeDir" = "" ]; then
        # Ctrl-C.
        return
    fi

    cd ${selectedWorkTreeDir}
}

スクリーンショット 2017-10-19 23.16.56.png

選択した候補のpath部分をcdに渡して階層を移動しています。
長ったらしい関数名ですがcdw(tabキー)みたいな感じで使ってます。

Vimで使う

自分は普段使いのエディタがVimなのですが、Vimでfzfが使えるようにサポートされているところがpecoではなくfzfを使う理由の一つです。

そして本家でもvimプラグインとしても提供されています。
https://github.com/junegunn/fzf.vim

ctrlp.vimのようなファイル検索

大きいプロジェクトだとctrlp.vimよりも遥かに高速です。

" [Replace of ctrlp.vim] ========================================
" 
nnoremap <C-p> :FZFFileList<CR>
command! FZFFileList call fzf#run({
            \ 'source': 'find . -type d -name .git -prune -o ! -name .DS_Store',
            \ 'sink': 'e'})

MRU(Most Recently Used)

よくあるMRUのFZFインターフェース化

" [MRU] ========================================
"
command! Fmru FZFMru
command! FZFMru call fzf#run({
            \  'source':  v:oldfiles,
            \  'sink':    'tabe',
            \  'options': '-m -x +s',
            \  'down':    '40%'})

QuickFixの検索

なにかQuickFixに送って開かせるつもりだったもの(あまり使ってない)。

" [QuickFix] ===================================
" 
command! Fq FZFQuickfix
command! FZFQuickfix call fzf#run({
            \  'source':  Get_qf_text_list(),
            \  'sink':    function('s:qf_sink'),
            \  'options': '-m -x +s',
            \  'down':    '40%'})

" QuickFix形式にqfListから文字列を生成する
function! Get_qf_text_list()
    let qflist = getqflist()
    let textList = []
    for i in qflist
        if i.valid
            let textList = add(textList, printf('%s|%d| %s',
                \       bufname(i.bufnr),
                \       i.lnum,
                \       matchstr(i.text, '\s*\zs.*\S')
                \   ))
        endif
    endfor
    return textList
endfunction

" QuickFix形式のstringからtabeに渡す
function! s:qf_sink(line)
    let parts = split(a:line, '\s')
    execute 'tabe ' . parts[0]
endfunction

Qiitaなどで紹介されているfzf活用例

fadd

gitのdiff, addをfzf でインタラクティブに

Gitのunstageなファイルを選択してどんどんstageに移せます。
個人的には特に大きいプロジェクトではtig開くよりも軽いし、かなりおすすめです。

まとめ

個人的にはfzfはシンプルかつVimサポートもあり、お陰様でCLI生活を豊かにしてくている(たぶん)ので好きです。

pecoもfzfも使ったことなかったよって人は、内容的にはbash(zsh)の部分に関してはpecoでもfzfでも同じことができると思うので、まずは自分で比較してみて好きになれそうな方を使ってみるのがおすすめです👍