yiskw note

機械学習やプログラミングについて気まぐれで書きます

fzfを導入してterminalでの作業効率を爆上げする


概要

今更ながら,コマンドラインで曖昧検索を行えるfzfを導入してみました.
fzfを活用することで,様々なコマンドをより便利にすることができます.
今回はその設定に関して,こちらにメモを残しておきます.

fzfとは

fzfは,コマンドラインインタラクティブに曖昧検索を行えるツールです.
ファイルやディレクトリに限らず,コマンド履歴やプロセス,ホストネームやgitのコミットなど,あらゆるものをフィルタリングできます.

fzfを使ってできることの例

fzfを使うと様々なことができるようになります!
以下一例ですが,fzfを使用して設定した便利な機能について紹介します

プレビュー付きでファイルの検索

ファイル名をあいまい検索しつつ,プレビューも同時に見ることができます.

過去に訪れたディレクトリに移動

過去に訪れたことのあるディレクトリをあいまい検索し,そのディレクトリに移動することができます

プロセスを検索してkill

プロセスを曖昧検索してインタラクティブにkillすることができます.

Dockerイメージをインタラクティブに削除

Dockerイメージの名前をあいまい検索して,インタラクティブに削除できます

fzfをインストールする

それでは,fzfをインストールしていきましょう.
Macを使用している場合は以下を実行します.

brew install fzf

# To install useful key bindings and fuzzy completion:
$(brew --prefix)/opt/fzf/install

Ubuntuの場合はapt-getでインストールできます.

sudo apt-get install fzf

その他のOSに関しては,こちらを参照してください.

また,他に一緒に追加しておくと便利なbat, ripgrepも追加しておきます.

brew install ripgrep
brew install bat

いずれもcat, grepコマンドを進化系のようなツールとなっています.
その他のOSでのインストール方法やパッケージの詳細については,公式のページをご覧ください.

fzfを使ってみる

それではfzfを使ってみましょう.
あるコマンドの標準出力をパイプで渡すだけで,それらに対してあいまい検索を行うことができます.
この際カーソルの上下移動はそれぞれCtrl + kCtrl + jで,選択はEnterでできます.

find . | fzf

これを応用することで,カレントディレクトリからファイルをあいまい検索し,選択したファイルをvimで開くということもできます.

vim `find . -type f | fzf`

またこの際にfzfコマンドのオプションを追加することで,プレビューを表示することも可能です.

vim `find . -type f | fzf --preview 'bat  --color=always --style=header,grid {}' --preview-window=right:60%`

このように,fzfを使うことで,様々なコマンドをより便利に使用することができます.

またfzfの使い方として,入力の途中で**を使用して,その続きを検索することもできます.
こちらはファイル名に限らず,プロセスなどにも使用できます.

fzfの設定を行う

fzfでは,fzf実行時のデフォルトのコマンドやオプションを設定することができ,
レイアウトのオプションを固定することができます.(参考
また,Ctrl + r, CTR + zを実行時のコマンドやオプションも設定できます.
詳しくはこちらをご参照ください.
自分は以下のような設定を.zshrcに記述しました.

【2022.4.20 追記】
FZF_DEFAULT_OPTS--previewオプションを追加するとうまく機能しないことがあります.
詳しくは以下をご参照ください.

yiskw713.hatenablog.com

export FZF_DEFAULT_COMMAND='rg --files --hidden --follow --glob "!**/.git/*"'
export FZF_DEFAULT_OPTS="
    --height 40% --reverse --border=sharp --margin=0,1
    --prompt=' ' --color=light
"

# for finding files in current directories
export FZF_CTRL_T_COMMAND='rg --files --hidden --follow --glob "!**/.git/*"'
export FZF_CTRL_T_OPTS="
    --preview 'bat  --color=always --style=header,grid {}'
    --preview-window=right:60%
"

# Ref: https://wonderwall.hatenablog.com/entry/2017/10/06/063000
# コマンドが長すぎる時に?を押すと,全コマンドが見れる
export FZF_CTRL_R_OPTS="
    --preview 'echo {}' --preview-window down:3:hidden:wrap --bind '?:toggle-preview'
"

便利な関数を定義する

それではfzfを使用した便利な関数を定義していきます.
自分はfzfを使った関数は,全てfから始まる名前にしていますが,好みに合わせて適宜名前は変更してください
多くは他のサイトからの引用してきたものですので,詳細に関しては元のサイトをご覧ください.

fgc: インタラクティブgit checkoutする

# fgc (git checkout) - checkout git branch including remote branches
# ref: https://qiita.com/kamykn/items/aa9920f07487559c0c7e
fgc() {
  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/[^/]*/##")
}

flog: gitのコミットログをインタラクティブに見る

# flog - git commit browser
# ref: https://qiita.com/kamykn/items/aa9920f07487559c0c7e
flog() {
  git log --graph --color=always \
      --format="%C(auto)%h%d %s %C(#C0C0C0)%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
             "
}

fcd: インタラクティブcdする

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

fadd: インタラクティブgit addするファイルを選択したり,diffを確認する

# fadd: git add / diff をインタラクティブに.Ctrl-d で diff, Enter で add
# https://qiita.com/reviry/items/e798da034955c2af84c5
fadd() {
  local out q n addfiles
  while out=$(
      git status --short |
      awk '{if (substr($0,2,1) !~ / /) print $2}' |
      fzf-tmux --multi --exit-0 --expect=ctrl-d); do
    q=$(head -1 <<< "$out")
    n=$[$(wc -l <<< "$out") - 1]
    addfiles=(`echo $(tail "-$n" <<< "$out")`)
    [[ -z "$addfiles" ]] && continue
    if [ "$q" = ctrl-d ]; then
      git diff --color=always $addfiles | less -R
    else
      git add $addfiles
    fi
  done
}

fvim: カレントディレクトリから編集可能なファイルを選択してvimで開く

自分は,fvというエイリアスも追加しています

# fvim: ファイル名検索+Vimで開くファイルをカレントディレクトリからfzfで検索可能に
# ref: https://momozo.tech/2021/03/10/fzf%E3%81%A7zsh%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96/
fvim() {
  local file
  file=$(
         rg --files --hidden --follow --glob "!**/.git/*" | fzf \
             --preview 'bat  --color=always --style=header,grid {}' --preview-window=right:60%
     )
  vi "$file"
}
alias fv="fvim"

Ctrl + z: かつて訪れたディレクトリに移動する

# かつていたことのあるディレクトリに移動する
# https://qiita.com/kamykn/items/aa9920f07487559c0c7e
fzf-z-search() {
    local res=$(z | sort -rn | cut -c 12- | fzf)
    if [ -n "$res" ]; then
        BUFFER+="cd $res"
        zle accept-line
    else
        return 1
    fi
}

zle -N fzf-z-search
bindkey '^z' fzf-z-search

fkill: インタラクティブにプロセスをkillする

# プロセスをkill
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
}

fdcnte: 実行中のコンテナを選択して,シェルにログインする

dcntedocker container execの略です.

# fzfでdockerコンテナに入る
# ref: https://momozo.tech/2021/03/10/fzf%E3%81%A7zsh%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96/
fdcnte() {
  local cid
  cid=$(docker ps | sed 1d | fzf -q "$1" | awk '{print $1}')
  [ -n "$cid" ] && docker exec -it "$cid" /bin/bash
}

fdl: dockerコンテナを選択し,ログを見る

# fzfでdockerのログを取得
# ref: https://momozo.tech/2021/03/10/fzf%E3%81%A7zsh%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96/
fdl() {
  local cid
  cid=$(docker ps -a | sed 1d | fzf -q "$1" | awk '{print $1}')
  [ -n "$cid" ] && docker logs -f --tail=200 "$cid"
}

fcntre: dockerコンテナを選択して,再起動する

# fzfでDockerコンテナ再起動
# ref: https://momozo.tech/2021/03/10/fzf%E3%81%A7zsh%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96/
fdcntre() {
  local cid
  cid=$(docker ps -a | sed 1d | fzf -m -q "$1" | awk '{print $1}')
  [ -n "$cid" ] && echo $cid | xargs docker container restart
}

fdcntrm: dockerコンテナを選択して,削除する

# docker container rm
# ref: https://momozo.tech/2021/03/10/fzf%E3%81%A7zsh%E3%82%BF%E3%83%BC%E3%83%9F%E3%83%8A%E3%83%AB%E4%BD%9C%E6%A5%AD%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96/
fdcntrm() {
  local cid
  cid=$(docker ps -a | sed 1d | fzf -m -q "$1" | awk '{print $1}')
  [ -n "$cid" ] && echo $cid | xargs docker container rm -f
}

fdimgrm: dockerイメージを選択して,削除する

# docker image rm
fdimgrm() {
  local cid
  cid=$(docker image ls -a | sed 1d | fzf -m -q "$1" | awk '{print $1}')
  [ -n "$cid" ] && echo $cid | xargs docker image rm -f
}

まとめ

今回はfzfを使ってみました.
非常に便利で,カスタマイズのしがいもあるので,今後もより良い使い方を模索していきたいです.

参考