インタラクティブフィルターとは
ものすごいざっくりと説明すると以下のような感じ
- リスト表示する系コマンドの出力結果を受け取ってインタラクティブに選択させてくれるやつ
-
リスト表示 -> 絞り込み -> 選択 -> 実行
の4ステップをシームレスにつなぐにくいやつ - 現代CLIの革命児
代表例として以下のようなコマンドがあります
インタラクティブフィルターの起動方法の違いについて
** 注:以下の説明はzshを前提にしています。bashやfishな方は適宜読み替えてください。**
早速実例と行きたいところですが、その前にインタラクティブフィルター(長いので以下IF
)の起動方式の違いについて述べたいと思います。
はやくソース見してという方は飛ばして下に行っていただければと思います。
IFの表示情報ソース。つまりはリスト表示するコマンドの出力やその加工処理は、都度入力するには少々複雑で長いものになりがちです。
これを覚えて実行することは難しいため、多くの方はエイリアスに登録するなどしてすばやく入力できるようにしているかと思います。
具体的にはカレントディレクトリ配下を検索してからディレクトリ移動するときはfd
というエイリアスを登録するとか。
意味のあるコマンドの集合をすばやく入力する方法はいくつかあり、以下のような方法があります。
- エイリアス
- グローバルエイリアス
- キーバインド
それぞれの方法に特徴があり、登録する処理毎に適切なものや不適切なものがあります。
順に解説していきましょう
エイリアス
エイリアスはIFの起動方法としてよく採用されるものかと思います。
私は以下のようなスニペットを用いて毎回自分のエイリアスを作成しています。
function {関数}() {
local HOGE=$({リストコマンド} | fzf +m)
if [ -n "$HOGE" ]; then
{実行コマンド} $HOGE
fi
}
alias {エイリアス名}={関数}
上記スニペットでやっているのは以下のような流れです。
- alias指定時に実行する関数を作る
- 選択するコマンドを実行した後にIFを実行して絞り込みを行い、結果を変数に格納
- 選択された結果があるかを確認
- このワンステップを踏まないとIF起動後に
Ctrl+c
でキャンセルしたときに最後まで実行されてしまいます
- このワンステップを踏まないとIF起動後に
- 選択された結果に従ってコマンドを実行
- aliasに関数を登録
直接エイリアスに書いてもいいのですが、私は制御をはさみたいので本処理は基本的に関数にしています。
IFの起動方法としてエイリアスを採用した場合の特徴としては以下のような感じでしょうか。
- キーバインド枯渇の心配がない
- 自分で打ちやすいエイリアスを登録すればいいのでほぼ無限大に増やせる
- コマンド実行履歴がエイリアスになる
グローバルエイリアス
エイリアス登録時に-g
をつけるとグローバルエイリアスになります。
グローバルエイリアスは登録したエイリアスを通常のスクリプトの間に展開することが可能です。
これはzshの機能ですね。詳しくはこちら
例えば以下はGitのブランチをIFで選択するグローバルエイリアスです。
alias -g B='`git branch --all | grep -v HEAD | fzf -m | sed "s/.* //" | sed "s#remotes/[^/]*/##"`'
使用する際は、以下のようになります。
$ git checkout B
$ git branch -D B
$ git branch -M B new_branch_name
グローバルエイリアスB
が展開され、ブランチ名になります。
グローバルエイリアスには以下のような特徴があります。
- エイリアスに比べIF実行結果を受け取った後の実行コマンドを柔軟に変更できるため汎用性が高い
- エイリアスに比べ制御処理が挟み込めない
-
Ctrl+c
でキャンセルしても構わず実行される
-
- コマンド実行履歴がグローバルエイリアス展開前のものになる
- 打ちやすくて適当なグローバルエイリアスのネーミングはむずい(個人の感想です)
キーバインド登録
キーバインドに指定した関数を実行させることもできます。
私がいつも使っているスニペットは以下のような感じです。
function {関数}() {
local HOGE=$({リストコマンド} | fzf +m)
if [ -n "$HOGE" ]; then
CURSOR="{実行コマンド} $HOGE"
fi
zle accept-line
}
zle -N {関数}
bindkey {キーバインド} {関数}
上記スニペットでやっているのは以下のような流れです。
- キーバインド押下に実行する関数を作る
- 選択するコマンドを実行した後にIFを実行して絞り込みを行い、結果を変数に格納
- 選択された結果がある場合
- zshのカーソルに最終的に実行するコマンドを代入する
- zshのカーソルに入力されたコマンドを実行する
- 関数をwidgetとして登録
- コレやらないとキーバインドに登録できない
- キーバインドにwidgetを割り当て
エイリアスと似た感じですが違うところがあって、
IFによる絞り込み結果を反映したコマンドを単純に実行するのではなく、
zshのカーソル(CURSOR
)に代入した後にzle accept-line
実行しているところです。
これは平たく言うと、擬似的にユーザが最終的なコマンドを実行する手順を模倣しているということで、
なにが嬉しいかというと、エイリアスではなく最終的なコマンドが履歴に残ることです。
これ以外にも細かく制御できることがあって、
以下のようにすると、カーソルに最終的なコマンドを代入して止めるなんてこともできます。
function {関数}() {
local HOGE=$({リストコマンド} | fzf +m)
if [ -n "$HOGE" ]; then
CURSOR="{実行コマンド} $HOGE"
else
zle accept-line
fi
}
zle -N {関数}
bindkey {キーバインド} {関数}
なおbashでもキーバインド登録は可能なので同じことはできそうですね。
細かい制御がどこまででできるかは知りませんが。
便利のコマンド集
以下は私がよく使用するものです。dotfilesからそのまま引っ張ってきた。
もっと知りたいならfzfのwikiを見るべし
ディレクトリ配下のディレクトリに移動する(find > cd)
cd-fzf-find() {
local DIR=$(find ./ -path '*/\.*' -name .git -prune -o -type d -print 2> /dev/null | fzf +m)
if [ -n "$DIR" ]; then
cd $DIR
fi
}
alias fd=cd-fzf-find
ディレクトリ配下のファイルをVimで開く(find > vim)
vim-fzf-find() {
local FILE=$(find ./ -path '*/\.*' -name .git -prune -o -type f -print 2> /dev/null | fzf +m)
if [ -n "$FILE" ]; then
${EDITOR:-vim} $FILE
fi
}
alias fv=vim-fzf-find
Gitリポジトリに移動する(ghq list > cd)
ghqが必要です
function cd-fzf-ghqlist() {
local GHQ_ROOT=`ghq root`
local REPO=`ghq list -p | sed -e 's;'${GHQ_ROOT}/';;g' |fzf +m`
if [ -n "${REPO}" ]; then
BUFFER="cd ${GHQ_ROOT}/${REPO}"
fi
zle accept-line
}
zle -N cd-fzf-ghqlist
bindkey '^G' cd-fzf-ghqlist
Gitブランチを切り替えする(git branch > git checkout)
function checkout-fzf-gitbranch() {
local GIT_BRANCH=$(git branch --all | grep -v HEAD | fzf +m)
if [ -n "$GIT_BRANCH" ]; then
git checkout $(echo "$GIT_BRANCH" | sed "s/.* //" | sed "s#remotes/[^/]*/##")
fi
zle accept-line
}
zle -N checkout-fzf-gitbranch
bindkey '^O' checkout-fzf-gitbranch
Gitブランチ指定を楽にする
alias -g B='`git branch --all | grep -v HEAD | fzf -m | sed "s/.* //" | sed "s#remotes/[^/]*/##"`'
使用シーン
$ git checkout B
$ git branch -D B
$ git branch -M B new_branch_name
コマンド履歴(history > コマンドラインバッファー)
function buffer-fzf-history() {
local HISTORY=$(history -n -r 1 | fzf +m)
BUFFER=$HISTORY
if [ -n "$HISTORY" ]; then
CURSOR=$#BUFFER
else
zle accept-line
fi
}
zle -N buffer-fzf-history
bindkey '^R' buffer-fzf-history
登録サーバにSSH(cat ~/.ssh/config > ssh)
function ssh-fzf-sshconfig() {
local SSH_HOST=$(awk '
tolower($1)=="host" {
for (i=2; i<=NF; i++) {
if ($i !~ "[*?]") {
print $i
}
}
}
' ~/.ssh/config | sort | fzf +m)
if [ -n "$SSH_HOST" ]; then
BUFFER="ssh $SSH_HOST"
fi
zle accept-line
}
zle -N ssh-fzf-sshconfig
bindkey '^\' ssh-fzf-sshconfig