bash で fzf を使い history の重複を排除して選択する

シェアする

fzf はインタラクティブ・フィルタリングツールというものの一つらしいです.日本では peco の方が有名かもしれません.今までは peco を使っていましたが気分転換に fzf に乗り換えてみました.詳しくは各々のGitHubを御覧ください.

私が特に使用頻度の高い機能はC-rの置き換えでコマンド履歴をフィルタリングしながらカーソルキーで選択するものです.デフォルトの挙動に不満があったので関数をいじって上書きしました.前提として,キーバインドとbash-completionの設定を有効化している前提で進めていきます.

スポンサーリンク
レクタングル(大)広告

動作確認環境

  • Ubuntu 18.04: bash 4.4.19
  • MacOS 10.13.5: bash 3.2.57

不満点

デフォルトでは下記のような設定になっています.

__fzf_history__() (
  local line
  shopt -u nocaseglob nocasematch
  line=$(
    HISTTIMEFORMAT= history |
    FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
    command grep '^ *[0-9]') &&
    if [[ $- =~ H ]]; then
      sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
    else
      sed 's/^ *\([0-9]*\)\** *//' <<< "$line"
    fi
)

これでは,履歴番号が表示される,同じコマンドの履歴が複数ある場合絞り込んだときに全く同じコードで埋め尽くされる,という不満がありました.

変更後

~/.bashrcに下記のように上書きしました.

__fzf_history__() {
  if type tac > /dev/null 2>&1; then tac="tac"; else tac="tail -r"; fi
  shopt -u nocaseglob nocasematch
  echo $(HISTTIMEFORMAT= history | command $tac | sed -e 's/^ *[0-9]\{1,\}\*\{0,1\} *//' -e 's/ *$//' | awk '!a[$0]++' |
      FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd))
}

C-rで動くようにするには次のコードを直後に入れる必要があります.

bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'

このコードは,履歴の番号を削除しています.また,過去に同じコマンド履歴が複数あった場合は最新の方だけを残して古い方は表示しないようにしています.末尾に複数個空白がある場合も重複しないようにしています.sedコマンドはPOSIX準拠で作成したのでMacでもLinuxでも動くはずです(私の環境では動きました).

まとめ

動かすだけなら簡単でしたが,MacとLinux両方で動くようにするのが大変でした.逆順に変換するためのtacがmacにはなかったり,逆にその互換機能を持つtail -rはLinuxには存在しないということや,sedも最初は拡張正規表現を使っていたのですがLinuxとbsdでは挙動が違うのでPOSIX準拠で書き直したりと,気をつけることが多かったです.

参考文献

スポンサーリンク
レクタングル(大)広告

シェアする

フォローする

スポンサーリンク
レクタングル(大)広告