Fuzzy Finder略して「fzf 」とは、コマンドライン上で動作するインタラクティブなフィルターツールです。標準入力から受け取ったリストからユーザーが対話的に選択を行い、その結果を標準出力に出力する、一言で言えばpeco によく似たツールです。
今回はUbuntu上でfzfを便利に使うレシピを紹介します。
fzfのインストール
Ubuntuにはfzfのパッケージはまだないため、gitのリポジトリからインストールします。ホームディレクトリ直下の「.fzf」ディレクトリにfzfのリポジトリをcloneしてください。リポジトリ内にあるインストール用のシェルスクリプトを実行します。
fzfのcloneクローン
$ git clone https://github.com/junegunn/fzf.git ~/.fzf
インストールスクリプトの実行例。~/.fzf.bashが生成され、.bashrcが変更されている
$ ~/.fzf/install
Downloading bin/fzf ...
Do you want to enable fuzzy auto-completion? ([y]/n) y
Do you want to enable key bindings? ([y]/n) y
Generate ~/.fzf.bash ... OK
Do you want to update your shell configuration files? ([y]/n) y
Update /home/mizuno/.bashrc:
- [ -f ~/.fzf.bash ] && source ~/.fzf.bash
+ Added
Finished. Restart your shell or reload config file.
source ~/.bashrc # bash
Use uninstall script to remove fzf.
For more information, see: https://github.com/junegunn/fzf
インストールスクリプトは、
fzfの実行バイナリのダウンロード
fzf設定ファイル(~/.fzf.bash)の作成[1]
を行っています。設定ファイルの中ではダウンロードしたバイナリへのPATHの設定の他、対話的な選択肢に応じてさらに以下に述べる設定が行われます。
「Do you want to enable fuzzy auto-completion?」は、fzfコマンド用のauto-completionを読み込むかどうかです。通常は「y」で構わないでしょう。「 y」と回答すると、~/.fzf.bashの中で「~/.fzf/shell/completion.bash」が読み込まれます。
「Do you want to enable key bindings?」は、fzfを利用した便利な機能を特定のキーにバインドし、シェルの機能を拡張するかどうかです。ただしシェルの標準の機能(たとえばCtrl+rで履歴の検索)をfzfで置き換えてしまうため、使い慣れた設定を書き換えられるのが嫌な場合は「n」を選択してもよいでしょう[2] 。「 y」と回答すると、.fzf.bashの中で「~/.fzf/shell/key-bindings.bash」が読み込まれます。
これらの2項目は「n」を選択しても、~/.fzf.bashの中にコメントアウトされた状態で設定が記述されるので、コメントを解除するだけで後からも有効にできます。
「Do you want to update your shell configuration files?」は、シェルの起動時に.fzf.bashを読み込むかどうかです。具体的には「y」を選択すると、~/.bashrcの最後に以下の一行が追記されます。
~/.bashrcから~/.fzf.bashを読み込む設定
[ -f ~/.fzf.bash ] && source ~/.fzf.bash
インストールが完了したら、手動で~/.fzf.bashを読み込むか、シェルを再起動してください。
fzfのアンインストール
~/.fzf/uninstallを実行すると、installスクリプトが生成した設定ファイルと、シェルの設定ファイルに追記した読み込み設定が削除されます。あとはcloneした~/.fzfディレクトリを削除すれば、インストールの前の状態に戻れます。
fzfのアンインストール
$ ~/.fzf/uninstall
$ rm -rf ~/.fzf
fzfの基本的な使い方
それでは実際にfzfを使っていきましょう。fzfの主なキー操作は表の通りです。
↑ or CTRL+p or CTRL+k
カーソルを上に移動する
↓ or CTRL+n or CTRL+j
カーソルを下に移動する
ENTER
カーソル位置にある項目、あるいはマークした項目を選択する
TAB or SHIFT+TAB
カーソル位置にある項目にマークをつける(マルチセレクトモード時のみ)
ESC or CTRL+c or CTRL+g
fzfを終了する
前述の通り、fzfは標準入力からリストを受け取り、それをフィルタするのが基本です。たとえば以下はfindコマンドでカレントディレクトリ以下にあるディレクトリのリストを出力し、それをfzfでフィルタする例です。
カレントディレクトリにあるディレクトリの一覧を対話的に選択する例
$ find . -maxdepth 1 -type d | fzf
fzf上で検索ワードを入力すると、リストから候補を絞り込めます。リストの項目が多い場合は、全体を目視できる程度まで検索で絞り込み、最後にカーソルを使って候補を選択するのが基本です。
図1 findコマンドの出力をfzfに渡した状態。ここから検索ワードを入力して絞り込んだり、カーソルで項目を選択する
図2 「 mai」と入力してみたところ。Mailディレクトリはもちろんのこと、あいまい検索によりこれらのアルファベットを含む「.thumbnails」ディレクトリも候補にあげられた
図3 絞り込んだリストから「Mail」を選択してEnterを押したところ。標準出力に選択した項目が表示された
これだけでは選択したディレクトリ名が標準出力に表示されるだけなので、何の役にも立ちませんよね。そこでフィルタした結果をコマンドに埋め込んだり、パイプで他のコマンドに渡してみましょう。
たとえば上記のフィルタ結果をduコマンドの引数に埋め込んでみましょう。これで、対話的に選択したディレクトリの容量をチェックするワンライナーになります。
選択したディレクトリをduコマンドに渡し、容量を計算する例
$ du -hs $(find . -maxdepth 1 -type d | fzf)
117M ./Mail
「-m」オプションをつけると、fzfはマルチセレクトモードになります。このモードでは、複数の項目にTABキーでマークをつけられます。マークがついた状態でENTERキーを押すと、マークした(複数の)項目が標準出力に渡されます。なおマークをひとつもつけずにENTERキーを押すと、シングルセレクトモードと同等の挙動となります[3] 。
[3] ひとつ以上の項目にマークをつけた状態で、マークのついていない項目の上にカーソルを動かしてENTERを押した場合、その(カーソルの下にあるマークのついていない)項目は選択されません。当然の挙動だとは思いますが、念の為。
前述の例をマルチセレクトモードで動かしてみましょう。選択した複数のディレクトリの容量をチェックできるようになりました。
複数のディレクトリを選択し、それぞれの容量を計算する例
$ du -hs $(find . -maxdepth 1 -type d | fzf -m)
4.2G ./ownCloud
13G ./Downloads
30M ./Desktop
117M ./Mail
なおパイプを繋がずにfzfを単独で起動すると、内部でfindコマンドを使ってカレントディレクトリ以下のファイルのリスト[4] を読み込みます。
fzfを単体で起動した際に呼ばれるコマンドは、環境変数「FZF_DEFAULT_COMMAND」で変更できます。たとえば以下のように環境変数を設定しておくと、findコマンドのかわりにpsコマンドが実行され、プロセスの一覧が候補リストとして渡されます。
パイプを繋がずにfzfを起動した際の挙動を変更する
$ export FZF_DEFAULT_COMMAND='ps aux'
$ fzf
fzfでシェルを便利に使う
インストール時にfzfキーバインドを有効にしているのであれば、以下の機能が使えます。
CTRL+r
CTRL+rにはシェルのコマンド履歴をインクリメンタルサーチする機能(reverse-search-history)が割り当てられていますが、これを__fzf_history__というシェル関数に置き換えています。
CTRL+rキーを押すと、historyコマンドが実行され、その結果がfzfコマンドに渡されます。ユーザーが呼び出したい履歴を選択すると、sedで不要なヒストリー番号を削除したものがコマンドラインに流し込まれます。具体的な実装は~/.fzf/shell/key-bindings.bashを参照してください。
図5 __fzf_history__を実行したところ。履歴をあいまい検索できるのは、完全に一致しなければならないインクリメンタルサーチに比べて非常に強力
シェルを使い込めば使い込むほど、履歴から複雑なコマンドを頻繁に呼び出すようになるでしょう。強力なあいまい検索をシェルに組み込めるこの機能は、fzfのメリットをもっとも簡単に実感できる機能のひとつです[5] 。
[5] 通常のシェルの履歴検索の場合、ENTERキーで履歴を選択した途端にそのコマンドが実行されますが、間にfzfを挟んでいる都合上、ENTERキーを2回押さなければならないのは、慣れるまで面倒かもしれません。
CTRL+t
CTRL+tキーを押すと、カレントディレクトリ以下にあるファイルとディレクトリを検索し、その結果がfzfに渡されます。冒頭で紹介したfindコマンドの例とほぼ同じ機能がキーに割り当てられていると考えてください。
「あるコマンドの引数にファイルを渡したいのだけれど、ディレクトリの階層が深くてシェルの補完では面倒」「 そもそもこのコマンドのオプション引数は補完できない」といった場合に活用できそうです。
ALT+c
CTRL+tによく似た機能です。カレントディレクトリ以下にあるディレクトリのみを検索し、その結果をfzfに渡します。その後、fzfで選択したディレクトリに自動的にcdします。
これも、深い階層のディレクトリに移動しなければならないのだけれど、シェルの補完では面倒という場面で便利です。
fzfの補完機能
もともとbashにはファイル名を補完する機能がありますが、TABキーを押しても候補の一覧が表示されるだけで、結局候補が一意に定まるまで、ある程度自分でキーをタイプしなければなりません。
fzfがインストールされている環境では、連続した2個のアスタリスク(**)を入力した状態でTABキーを押すと、あいまい補完機能が起動します。fzfのあいまい検索機能を使って候補を絞り込み、選択した対象をコマンドラインに挿入できます。それでは実際に使ってみましょう。
例としてcatコマンドの引数としてアスタリスクを2個入力し、TABキーを押します。するとカレントディレクトリ以下のファイルとディレクトリのあいまい補完が起動します。この際のfzfはマルチセレクトモードで起動しているため、複数のファイルをマークできます。ENTERを押せば選択したファイルがcatコマンドの引数として挿入されます。
catコマンドの引数をあいまい補完で入力する
$ cat **(TABを押す)
図5 catコマンドの引数に渡すファイルを、あいまい補完で検索しようとした例。ここでENTERを押すと、ownCloud/Write/Recipe/504/504.md(本記事の原稿ファイル)が引数として挿入される
あいまい補完はzshの補完のように、文脈を読んで補完候補をリストアップしてくれます。たとえばsshコマンドの引数を補完する場合は、「 ~/.ssh/config」「 ~/.ssh/known_hosts」「 /etc/ssh/ssh_config」「 /etc/hosts」といったファイルから、接続設定のあるホスト名や、過去に接続したホスト名を集めてきます。
図6 sshコマンドの引数として、接続先のホストをあいまい補完した例。過去に接続したホストのIPアドレスが補完候補にあげられている
あいまい補完のトリガーとなる文言は、環境変数FZF_COMPLETION_TRIGGERで変更できます。たとえば以下のように環境変数を設定すると、「 hoge」と入力してTABキーを押すことで、あいまい補完が起動します。
あいまい補完のトリガー文字列を「hoge」に設定した例
$ export FZF_COMPLETION_TRIGGER=hoge
$ ls hoge(TABを押す)
図7 hogeという文言をトリガーとしてあいまい補完を起動した状態。ENTERを押すと、当然hogeは選択したファイル名で置き換えられる
どのコマンドにどんな補完候補が用意されているか、詳しく知りたい場合は~/.fzf/shell/completion.bashを参照してください。
レイアウトを変更する
fzfのデフォルトでは、端末の画面全体を使い、下から上にボトムアップのレイアウトで候補が列挙されました。ところがあいまい補完等では、端末内の一部分だけに候補が列挙されています。こういったレイアウトの変更はオプションで可能です。
--height
候補一覧が表示される高さをパーセンテージで指定します。
端末の半分のサイズでfzfを起動する
$ fzf --height 50%
--reverse
検索ワードの入力エリアが最上部に表示し、そこから画面の下に向かって候補が並ぶトップダウンのレイアウトに変更します。一般的なCLIアプリのレイアウトとしては、こちらの方が自然かもしれません。
トップダウンレイアウトでfzfを起動する
$ fzf --reverse
--tac
tacコマンドのように、入力された候補一覧の並びを逆にして表示します。リストの昇順、降順を変更したい場合などに利用します。
リストの昇順、降順を逆にする
$ fzf --tac
--border
候補一覧のまわりにボーダーラインを描画します。見やすくなるのでおすすめです。
ボーダーラインを表示する
$ fzf --border
オプションを常に適用するには
こうしたオプションを、毎回指定するのは面倒な上、コマンドも長く、見通しが悪くなってしまいます。fzfは環境変数FZF_DEFAULT_OPTSに指定されたオプションを自動で適用するため、毎回利用するオプションはこの変数に設定しておくとよいでしょう。
常時50%のサイズでボーダーつきの領域に、上下逆に表示させる
$ export FZF_DEFAULT_OPTS='--height 50% --reverse --border'
図8 上記環境変数を指定してfzfを起動した例
他にもマージンの指定や検索プロンプトの変更などが可能です。詳しくはmanのLayoutセクションを参照してください。
プレビュー機能を使う
fzfにはプレビュー機能が備わっています。端末を候補一覧とプレビュー画面に分割し、カーソルを移動して候補を選択した際に、プレビュー画面に任意のコマンドの結果を表示させることができます。
たとえば以下は、システムにインストールされているパッケージの一覧と、そのパッケージに含まれるファイルの一覧をプレビューする例です。
まずdpkgコマンドでシステムにインストールされているパッケージの一覧を取得し、fzfに渡しています。「 --preview」オプションには、プレビューの表示に利用するコマンドを指定します。ここでは引数に指定したパッケージに含まれるファイルの一覧を表示する「dpkg -L」コマンドを指定しています。「 {}」は、現在カーソルの下にある項目(ここではパッケージ名)に置換されます。
インストールされているパッケージの一覧と、選択したパッケージに含まれるファイル一覧をプレビューする例
$ dpkg --get-selections | awk '{print $1}' | \
fzf --preview 'dpkg -L {}' --reverse --border
図9 上記コマンドを使ったパッケージ情報のプレビュー画面
以下はシェル関数を定義し、「 man -k」で検索したmanページ群をプレビューする例です。manページの場合はプレビュー画面が狭いと読みづらいと思い、「 --preview-window」オプションで端末を上下分割した上で、項目一覧とプレビュー画面の比率を3:7にしています。
引数に指定されたキーワードでmanページを検索し、fzfに渡してプレビュー表示するシェル関数の例。ここではopenというキーワードでマニュアルを探している
$ function manf {
man -k $1 | awk '{print $1}' | \
fzf --preview 'man {}' --height 100% --reverse --preview-window down:70%;
}
$ manf open
図10 上記シェル関数を使ったmanのプレビュー画面
他にも「tarボールの中身を表示する」「 tivを使って画像をプレビューする」など、色々な応用ができそうですね。
さて、パッケージのファイルリストやマニュアルのプレビューが表示されるのは非常に便利なのですが、ここで「プレビューページがスクロールできない」ことに気付いてしまったのではないでしょうか?
fzfにはpreview-up/down、preview-page-up/downというアクションが用意されているのですが、デフォルトではこれらのアクションにキーが割り当てられていないため、プレビューページがスクロールできないのです[6] 。
そこで、プレビューを使う場合はこれらのアクションにキーを割り当てましょう。それには「--bind」オプションを使います。オプション引数として、割り当てるキーとアクションをコロンで繋いだペアを指定します。なおこのペアはカンマで区切って複数列挙できます。
前述のパッケージのファイル一覧をプレビューするワンライナーに、ALT+pとALT+nでプレビューページをスクロールする設定を足した例
$ dpkg --get-selections | awk '{print $1}' | \
fzf --preview 'dpkg -L {}' --reverse --border --bind alt-p:preview-up,alt-n:preview-down
まとめ
fzfはスクリプトや関数内に埋め込むことで、さまざまなコマンドの機能を強化できる可能性を秘めたフィルタです。公式ドキュメントのExamples には、fzfを利用した強力な応用例が豊富に紹介されています。ぜひ一度目を通してみてください[7] 。