【FreeBSD】fzfでサクッとパッケージ検索&インストールするシェルスクリプト

確認環境

PC Thinkpad X1 Carbon 2nd Gen
OS FreeBSD 12.0-RELEASE-p7
fzf 0.18.0 (0b33dc6)
sudo version 1.8.27

課題

pkg searchpkg infoコマンドで、目的の機能を持つパッケージを探すのは難しい。

キーワードでGrepかけた結果をlessvimとにらめっこ。。。

「うーん、無さそうだな」

というときは、またキーワードを変えてGrepから。

訂正したところで、また同じことの繰り返し。

もう、そういうのうんざりなんだよ。

対策

もっと楽に連続的に検索したい。選択したい。インストールしたい。

おや、、、、、、探して、選んで、何かする。。。

fzf案件ですな。

ということで、pkgのパッケージリストから、fzfで選択して、インストールや削除するシェルスクリプトを作ってみよう。

以下は、作成したシェルスクリプトを起動して、イメージビューワーを探して、fehというパッケージを選んで、インストールする様子の動画。
fzfのプレビュー画面には、パッケージの詳細情報を表示するようにした。
また、fzfなら綴りを間違えてもいい感じに拾ってくれる。

以降では、以下について説明する。

  1. コマンド概要
  2. シェルスクリプトの使用例
  3. 必要なもの
  4. コード
    • 補足) fzfとの連携処理
    • 補足) 他の環境のパッケージ管理コマンドに置き換える場合

1. コマンド概要

コマンドの概要は以下。

  1. pkg searchまたはpkg infoからパッケージリストを作成し、fzfに渡す
  2. fzfでパッケージを選ぶ。fzfのプレビュー画面には、パッケージ詳細情報を表示
  3. 選んだパッケージに対して、どんなアクションをするか選ぶ
    • パッケージのインストール
    • パッケージの削除
    • パッケージ詳細情報を出力
    • パッケージ名の出力

アクションは、コマンドオプションで予め選択できる。

"pkg" wrapper command with fzf  

USAGE:  
    fpf [-l] [-nivIRv] [package-name]  
      
OPTIONS:  
    [source]  
        -l  if set '-l', search installed local packages  
            if not set, search from all packages  
    [action]  
        -n  print name of selected packages  
        -i  print info of selected packages  
        -I  install selected packages  
        -R  remove selected packages  
        -v  print this command version  

2. シェルスクリプトの使用例

以降、fpfというファイル名で上記コードを保存した前提で説明する。

アクション未指定で起動

コマンドオプションでアクションを指定しなかった場合、fzfで選んだ後にアクションを選ぶことになる。

> fpf                 # 全パッケージをパッケージリストにして起動  
> fpf pkg-name        # pkg-nameを含むパッケージに、対象リストを絞った状態で起動  
> fpf -l              # インストール済みパッケージのみを対象リストにする  
> fpf -l pkg-name     # インストール済みパッケージで、pkg-nameを含む  

fzfでの選択中は、プレビュー画面にカーソル行のパッケージの詳細情報を表示する。
?キーで、表示・非表示は切り替えられる。

パッケージ選択後の画面例は以下。
選んだパッケージ名を表示して、アクションを選択を促すプロンプトが出る。

# Selected packages
-------------------------------------------  
p5-WWW-Scraper-ISBN-ORA_Driver-0.23  
apache-openoffice-4.1.6_5  
apache-openoffice-devel-4.2.1856382_2,4  

# Please select action
-------------------------------------------  
 n : print name of selected packages  
 i : print info of selected packages  
 I : install selected packages  
 R : remove selected packages  
 q : quit  
-------------------------------------------  
>  

基本的には、後述のオプションと一緒。

  • n パッケージ名を出力
  • i パッケージの詳細情報を出力
  • I パッケージをインストール
  • R パッケージを削除
  • q 何もせずに終了

このアクション選択画面は、標準エラーに出力している。
そのため、本コマンドの標準出力は、指定したアクションの出力しか出ない。はず。

アクションを指定して起動

アクションは、予めオプションで指定できる。
指定した場合は、fzf選択後のアクション選択画面は出ない。

> fpf -n              # パッケージ名を行区切りで出力  
> fpf -i              # パッケージの詳細情報を出力  
> fpf -I              # パッケージのインストール  
> fpf -R              # パッケージの削除(インストール済みリストから探す)
> fpf -I pkg-name     # `-I`も`-R`も予めパケージ名から対象リストを絞って起動できる  
> fpf -IR             # 複数のアクションを指定した場合は後勝ち  
> fpf -Il             # `-I`指定時、`-l`は無効(インストール済みリストから探しても仕方ない)  

3. 必要なもの

今回のスクリプトでは、プリインストールされていない、以下のコマンドを利用している。

  • fzf 今回のメインである対話的にフィジカル検索できる子
  • sodo 管理者権限を一時的に授けてくれる子 (パッケージをインストール・削除するため)

どちらもpkgでインストールできるので、持ってない人はインストールしておこう。

> pkg install fzf sodo  

fzfについては、こちらの記事で紹介しているので、知らない人は覗いてみてねと。

4. コード

今回のコードはこちら。長い。
パスが通ったディレクトリに置いて、実行権限の追加もお忘れなく。
変なとこあれば、コメントでご指摘頂けると助かります。

ざっくり処理ブロックを説明すると以下。

  • 2行目以降 ヘルプ表示関連
  • 25行目以降 オプション&引数処理
  • 58行目以降 デバッグ用出力(無効化中)
  • 63行目以降 fzfとの連携処理
  • 77行目以降 オプション指定がない場合の、アクション選択
  • 114行目以降 アクションの実行

オプションやら対話的なアクション指定を増やしたせいで長くなった。
最初は50行程度だったのに。

本質的な部分は63行目以降のfzfとの連携部分だと思う。
ここについて、ちょっと補足しておこう。

補足) fzfとの連携処理

fzfとの連携箇所は以下の部分。

# select packages with fzf
PKG_NAME_PICK_PATTERN="s/^\\([^ ]*\\).*/\\1/"  
selected_pkgs=`$list_cmd $pkg_name          \  
    | fzf -m -q "$search_query"             \  
        --preview "echo {}                  \  
            | sed '$PKG_NAME_PICK_PATTERN'  \  
            | xargs $PKG_INFO_CMD"          \  
        --preview-window right:wrap         \  
        --bind '?:toggle-preview'           \  
    | sed -e "$PKG_NAME_PICK_PATTERN"`  

# cancelled?
[ -z "$selected_pkgs" ] && exit 0

PKG_NAME_PICK_PATTERNには、対象リストの1行から、パッケージ名のみ抜き出すsedのパターンを指定。

selected_pkgsに、fzfの選択したパッケージ名リストが入る。
このとき、選択したパッケージはpkg1 pkg2 pkg3 ...のように、空白区切りの1行になる。

$list_cmd $pkg_nameで、対象リストを作成するコマンドを指定。
pkg info -x -Ipkg search -xに、引数で指定したパッケージ名を渡す感じ。

fzf -m -q "$search_query"の行からfzfの起動オプションを指定。

  • -mで複数のパッケージを選択を許可
  • -q $search_queryは、空文字しかセットしていないので、今は実質使っていない
  • --preview "echo {}"からの3行で、プレビュー画面の設定をしている
    • sedでパッケージ名だけを抜き出して、xargspkg search -fへ渡している
    • --preview-window right:wrapで、プレビュー画面を右に表示する指定
      • プレビュー画面をデフォルトで非表示にする場合は、wraphiddenに変えるといい
    • --bind '?:toggle-preview'で、?キーによってプレビュー画面の表示・非表示を切り替える

最後に、sed -e "$PKG_NAME_PICK_PATTERN"により、選んだ各行をパッケージ名だけに置換えて、fzfによる選択したパッケージ名リストの作成終了。

[ -z "$selected_pkgs" ] && exit 0で、fzfで選択をキャンセルした場合(Escなど)、何もせずにスクリプトを終了する。

補足) 他の環境のパッケージ管理コマンドに置き換える場合

今回作成したものは、他の環境のパッケージ管理コマンドにも置換えやすいと思う。
(まぁ、きっと誰かがもう作っているだろうけど)

とりあえず、以下あたりを修正すれば良いと思われる。

  • 4行目付近: ヘルプ
  • 26行目(PKG_LIST_CMD_ALL): 全パッケージリストから検索するコマンド
  • 27行目(PKG_LIST_CMD_INSTALLED): インストール済みパッケージリストから検索するコマンド
  • 28行目(PKG_INFO_CMD): パッケージの詳細情報を表示するコマンド
  • 55行目(pkg_name): 初期値に、全リストにマッチするパターンを指定
  • 64行目(PKG_NAME_PICK_PATTERN): リストの1行からパッケージ名を抜き出すパターンを指定
  • 124行目: パッケージをインストールするコマンドを指定
  • 128行目: パッケージを削除するコマンドを指定

最後に

何かを「探す」という行為は、本来やりたいことの前処理だ。
理想は探さなくていいことなんだろうけど、fzfを使えば「探す」こと自体がちょっと楽しくなる。

今回は正直、パッケージ検索を楽にしたいというよりは、fzfと遊んでみたかっただけだった。

とはいえ、fzfを活用するスクリプトは、今回と同じような流れだと使い勝手が良い気がする。
もう少し整理すればテンプレートになるかもしれない。

むしろfzf連携シェルスクリプトを自動生成するスクリプトがいいのかも。
可変部分のコマンドやヘルプは別ファイルに書いておいて、スクリプトで自動生成。みたいな。

誰か作ってー。

参考

以上。