【FreeBSD】fzfでサクッとファイルを開きたい(on tcsh)

確認環境

PC Thinkpad X1 Carbon 2nd Gen
OS FreeBSD 12.0-RELEASE-p7
fzf 0.18.0 (0b33dc6)
tcsh tcsh 6.20.00 (Astron)

課題

シェルにファイルの補完機能があるとはいえ、深い階層のパスを指定してエディタで開くのは面倒だ。
もっとサクッとファイルを開く方法はないだろうか。

対策

fzfというフィルタリングツールを使うと幸せになれるだろう。

まずは、以下の動画を見て頂きたい。

www.youtube.com

ナイスガイなあんちゃんが何を喋っているのかサッパリだけれども、
このツールは、以下のような動作の流れになる。

  1. 標準入力からのデータを受け取り
  2. ユーザが任意の行を選ぶ。このとき曖昧検索により、リアルタイムで対象行をフィルタリングできる
  3. 最終的に選んだ行を標準出力に出力する

これが恐ろしく汎用的。
例えば以下のようなことができる。

  • コマンド履歴(historyなどの出力)から選択したコマンドを実行する
  • ディレクトリリスト(findなどの出力)から選択したディレクトリへ移動する
  • ログから怪しい箇所(grepなどの出力)を選択して、メールを送る
  • 関数の出現箇所(grepなどの出力)のソースを眺める

、、、などなど。夢が広がりますな。

fzfと似たようなものにpecoというツールもある。

今回は、主にfzftcshとの連携方法について紹介する。
どうやらtcshではB系シェルのような関数が作れないようなので、各操作用のシェルスクリプトを作っていく。

  1. fzfripgrepのインストール
  2. ~/.cshrcの設定
  3. ファイルを開く
  4. ディレクトリを移動する
  5. コマンド履歴から選んで実行する

1. fzfとripgrepのインストール

まずは、下記コマンドでfzfripgrepをインストールする。
(管理者権限で実行してね)

> pkg install fzf ripgrep  

ripgrepはRust製の爆速Grepツール。
fzfからripgrepを利用する。(他のGrepツールも指定可能)

fzfのインストールは、pkgコマンドの他、githubにインストール用のスクリプトが用意されている。
そちらを使えば、fzf起動後にキー操作により、検索対象をディレクトリに絞ったりということができるらしい。
興味がある人は、下記サイトあたりを参考にされると良いと思う。

qiita.com

2. ~/.cshrcの設定

fzfで使うGrepツールと、デフォルトで有効にするオプションを指定しよう。

~/.cshrcに下記を追記する。

setenv  FZF_DEFAULT_COMMAND 'rg --files --hidden --follow --glob "!\.git/*"'  
setenv  FZF_DEFAULT_OPTS "--height 80% --reverse --inline-info"  

FZF_DEFAULT_COMMAND

FZF_DEFAULT_COMMANDripgrepを使用するように指定している。
ripgrepのオプションの意味は以下。

  • --files ファイルを検索(find的な)
  • --hidden 隠しファイルも対象
  • --follow シンボリックリンク先も対象
  • --glob "!\.git/*" gitディレクトリ配下は対象外

FZF_DEFAULT_OPTS

FZF_DEFAULT_OPTSfzfでデフォルトで使いたいオプションを指定している。
fzfのオプションの意味は以下。

  • --helght 80% 表示する高さを今のシェルの80%に指定
  • --reverse フィルタ文字列入力行を一番下に表示する(デフォルトは一番下の行)
  • --inline-info フィルタ中にマッチ件数を表示する

動作確認

ここまでできたら、一度動作確認をしておこう。

> source ~/.cshrc
> fzf

Ctrl+j or Ctrl+nでカーソルを下に動かせる。
Ctrl+k or Ctrl+pでカーソルを上に動かせる。
Esc or Ctrl+d or Ctrl+cで、選択せずにキャンセルできる。
Enterキーで任意の行を選択すると、選択画面が閉じ、選択した行の文字列が標準出力に出ているはずだ。

3. ファイルを開く

以下をシェルスクリプトとして保存してパスが通ったディレクトリに置こう。
(実行権限を追加(chmod +x filename)するのもお忘れなく)

gist.github.com

カレントディレクトリ配下のファイルリストを作って、fzfに渡している。

引数が1つの場合は、カレントディレクトリ配下を、引数のパターンで検索。
引数が2つの場合は、第1引数のディレクトリ配下を、第2引数のパターンで検索。
パターンは、fzf起動後に打ち直せる。

fzfのオプションの意味は以下。

  • --query="$1" 予めフィルタ文字列を指定
  • --multi 複数ファイル選択可能にする(Tab/Shift+Tabで選択トグル)
  • --select-1 対象ファイルが1行だけだったら、選択画面を出さずに1行を出力
  • --exit-0 対象ファイルが0行だったら、即終了
  • --preview "head -n 100 {}" 選択中のファイルの最初の100行をプレビュー画面に表示している。

プレビュー画面はシンタックスハイライトして欲しいという人は、batというコマンドを試してみると良いかも。
(ただ、headに比べると若干ラグを感じる)

fzfEscキーなどによりキャンセルされたら、何もせずに終了する。

パスの通ったディレクトリに置いたら、実行権限を追加してコマンドを試してみよう。
(以下では、~/bin/feに保存した例)

> chmod +x ~/bin/fe  
> fe  
> fe py$  

4. ディレクトリを移動する

シェルスクリプトの中で変数を変更しても、スクリプトを実行した親プロセスには影響しない。
つまり、スクリプトの中でcdコマンドをしても、親プロセスには反映されない。

どうしたものかと考えてみたけど、sourceコマンドで反映する方法で対応してみた。
もっと良さ気な方法があれば、ご教示下さいまし。

まずは、sourceをかけるCシェルのスクリプトファイル。

gist.github.com

引数がない場合は、カレントディレクトリ配下のディレクトリリストを作って、fzfに渡す。
引数がある場合は、第1引数のパス配下のディレクトリリストを作って、fzfに渡す。
fzfEscキーなどによりキャンセルされたら、何もせずに終了する。
fzfで何か選択したら、ファイルを選択する。

findコマンドでカレントディレクト配下のディレクトリリストを作って、fzfに渡す。
(プレビューにツリーを表示するアイデアもあるみたい⇒こちら)

ちなみにgrepをかませている理由は、findのエラーを除去するため。
(Cシェルの標準エラーだけ捨てて、標準出力を次のパイプへ渡すやり方が分からない)

あとは、上記ファイルにsourceをかけるエイリアスを~/.cshrcに書くだけ。
パスの~/bin/fd.cshは、各自置いた場所に置き換えて下さいな。

alias fd   source ~/bin/fd.csh  

保存したら、コマンドがちゃんと動くか試してみよう。

> source ~/.cshrc  
> fd  

5. コマンド履歴から選んで実行する

最後のサンプルは、tcshの実行履歴をfzfで選んで実行する例。
ここではfzfをインストールしたら付いてくる、fzf-tmuxコマンドを使ってみる。
これは、fzfでの選択画面をtmuxの新しいペインに表示してくれる。
fzf実行後は、作成したペインを閉じる礼儀正しい子なの。

gist.github.com

fzf-tmuxに渡しているオプションの意味は下記。

  • --no-sort 入力データをソートしない。
  • --nth 3.. 3列目以降のフィールドを検索対象(今回の例では、実行コマンド文字列のみを対象にしている)
  • -d 90% fzfで使用するペインの高さを90%に指定

sedでごちゃごちゃやっているのは、履歴番号と時刻と空白を除去。
history -hとして、コマンドだけ表示にしてもいいかもしれない。
(その場合は、--nth 3も要らなくなる)

あとは、上記ファイルにsourceをかけるエイリアスを~/.cshrcに書くだけ。
パスの~/bin/fd.cshは、各自置いた場所に置き換えて下さいな。

alias fh   source ~/bin/fh.csh  

ちなみに、このコマンドは#!/bin/tcshを先頭行に書いて実行権限を与えれば動くとは思う。
ただ、あくまでtcshの履歴なので、他の自作の汎用的なshスクリプトと混じらないように明示したかっただけ。
各自、お好きにして下され。

保存したら、コマンドがちゃんと動くか試してみよう。

> source ~/.cshrc  
> fd  

最後に

対象リストを作って、絞って、選んで、何かを行う。

この恐ろしく汎用性の高いツールの使い道は、あなたの想像力次第。

FreeBSDで言えば、pkgコマンドとfzfを組み合わせると、検索&インストールが楽しくなりそう。
(2019/7/21 追記 ⇒ 作ってみたよ)

他に何か良さ気な使い方があれば、教えて下さいましまし。

Neovimとの連携については、そのうちまた紹介したい。

参考

以上。