32
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

iRidgeAdvent Calendar 2018

Day 24

fzfやpecoでインタラクティブ選択すると便利なコマンド集

Last updated at Posted at 2018-12-24

インタラクティブフィルターとは

ものすごいざっくりと説明すると以下のような感じ

  • リスト表示する系コマンドの出力結果を受け取ってインタラクティブに選択させてくれるやつ
  • リスト表示 -> 絞り込み -> 選択 -> 実行の4ステップをシームレスにつなぐにくいやつ
  • 現代CLIの革命児

代表例として以下のようなコマンドがあります

インタラクティブフィルターの起動方法の違いについて

** 注:以下の説明はzshを前提にしています。bashやfishな方は適宜読み替えてください。**

早速実例と行きたいところですが、その前にインタラクティブフィルター(長いので以下IF)の起動方式の違いについて述べたいと思います。

はやくソース見してという方は飛ばして下に行っていただければと思います。

IFの表示情報ソース。つまりはリスト表示するコマンドの出力やその加工処理は、都度入力するには少々複雑で長いものになりがちです。
これを覚えて実行することは難しいため、多くの方はエイリアスに登録するなどしてすばやく入力できるようにしているかと思います。
具体的にはカレントディレクトリ配下を検索してからディレクトリ移動するときはfdというエイリアスを登録するとか。

意味のあるコマンドの集合をすばやく入力する方法はいくつかあり、以下のような方法があります。

  • エイリアス
  • グローバルエイリアス
  • キーバインド

それぞれの方法に特徴があり、登録する処理毎に適切なものや不適切なものがあります。
順に解説していきましょう

エイリアス

エイリアスはIFの起動方法としてよく採用されるものかと思います。
私は以下のようなスニペットを用いて毎回自分のエイリアスを作成しています。

function {関数}() {
    local HOGE=$({リストコマンド} | fzf +m)
    if [ -n "$HOGE" ]; then
        {実行コマンド} $HOGE
    fi
}
alias {エイリアス名}={関数}

上記スニペットでやっているのは以下のような流れです。

  1. alias指定時に実行する関数を作る
    1. 選択するコマンドを実行した後にIFを実行して絞り込みを行い、結果を変数に格納
    2. 選択された結果があるかを確認
      • このワンステップを踏まないとIF起動後にCtrl+cでキャンセルしたときに最後まで実行されてしまいます
    3. 選択された結果に従ってコマンドを実行
  2. 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 {キーバインド} {関数}

上記スニペットでやっているのは以下のような流れです。

  1. キーバインド押下に実行する関数を作る
    1. 選択するコマンドを実行した後にIFを実行して絞り込みを行い、結果を変数に格納
    2. 選択された結果がある場合
      1. zshのカーソルに最終的に実行するコマンドを代入する
    3. zshのカーソルに入力されたコマンドを実行する
  2. 関数をwidgetとして登録
    • コレやらないとキーバインドに登録できない
  3. キーバインドに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
32
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?