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準拠で書き直したりと,気をつけることが多かったです.
参考文献
- GitHub: junegunn/fzf
- htlsne’s blog: fzfでコマンド履歴を選択
- Qiita @kmszk: fzfを活用してTerminalの作業効率を高める
- Qiita @hitohiro77: sedでこういう時はどう書く?
- hydroculのメモ: コマンドの使い方(Linux) > sed コマンド
- Qiita richmikan@github: どの環境でも使えるシェルスクリプトを書くためのメモ ver4.51
- 正規表現メモ/sedで使用できる正規表現演算子
- UNICAST LAB: grepによる正規表現と’-E’をつけた拡張正規表現の違い(and sed)
- Qiita @b4b4r07: 逆順出力 tac と tail -r
- Qiita @ritukiii: いい加減覚えよう。 `command > /dev/null 2>&1`の意味
- LESS IS MORE: Bashでコマンドの存在チェック
- Arabesqve: Bash history expansion
- man bash
- @IT: 過去に実行したコマンドを再実行するには
- stackoverflow: why “echo $-” outputs “himBH” on man bash shell?
- Qiita @incep: ログインシェルとインタラクティブシェルと~/.bashrc達の関係