この記事は株式会社ソラコム Advent Calendar 2021 の 14日目の記事です。
TL;DR;
- fzf の Custom fuzzy completion を使って、SORACOM CLI で imsi 等のリソースの値を、裏で CLI 呼んでいい感じに選択できるような設定を書いてみたで
- ここにおいとくから良かったら参考にしてな (GitHub)
- fzf の Custom fuzzy completion 、bash/zsh の 補完を自分で書くより簡単やからおすすめやで
SORACOM CLI
SORACOM は IoT プラットフォームとして通信を中心とした様々なサービスを提供していますが、その殆どの機能をユーザーコンソールや API のほかに CLI で操作することができます。aws cli などと似たような雰囲気で、例えば
$ soracom sims get-data --sim-id XXXXXXXXXXXXXXX
のようにすると、SORACOM Harvest Data に蓄積されている、SORACOM IoT SIM を搭載した IoT デバイスから送信されたデータを簡単に取得できるわけです。
そんな SORACOM CLI (、にかぎらずCLI コマンド全般ですが)は、ちょっとした操作や自動化、データ整形等にとっても便利ですが、多くあるコマンドやオプションを覚えるのは大変ですし、毎回マニュアルを見たり web で検索するのも面倒です。
そんなときに役立つのはシェルの補完機能です。コマンドを書きかけの状態で <TAB>
や <Ctrl+I>
キーを押すと、候補のオプションや値を表示して選択させてくれるえらいやつです。
SORACOM CLI でも bash/zsh 向けの補完機能が提供されていて、ご機嫌にオプションをバシバシ補完していけるわけですが、上の例の --sim-id
のような、静的に決まらない値は補完できないので、結局別の CLI 結果やユーザーコンソールからコピペすることになるわけです。困ったなー1
fzf の Fuzzy completion 機能
fzf は peco や percol と同じような、CLI で動作するインタラクティブなフィルターツールです。
普段から Unix 系 OS や wsl 等で CLI を利用される方は、いずれかのツールを利用されている方も多いのではないでしょうか?
私自身もよく使っていて、これらのツールが無いと生産性とやる気が30%ぐらい低下する気がします。
ところで fzf には、他のツールと同様に pipe して選択肢を絞り込んだりする他に、"Fuzzy completion" という機能が付属(というかスクリプトが同梱)しています。
この Fuzzy completion は、コマンドを入力中に **
をキーワードとして入力して <TAB>
キーを押すと、そのとき入力しているコマンドラインのコンテキストに応じて、いい感じに選択肢を表示 => fzfで絞り込んでその結果を元のコマンドラインに補完、ということをしてくれます。
こんな感じです。
いいですね。
その他の例は README を見てもらうのが早いのですが、kill **<TAB>
とするとプロセス一覧から fzf で選択できたり、export **<TAB>
すると変数一覧を出してくれたりして素敵なわけです。
さて、この Fuzzy completion はデフォルトを pwd
以下のファイルの補完としつつ、組み込みでいくつかのコマンドをサポートしているわけですが、自分で設定を書いて自分の好きなコマンドの補完をさせることもできます。
というわけで今回は、SORACOM CLI 向けの Custom Fuzzy Completion を書いてみて、もっといい感じに補完してやろうという試みです。
ちなみに、この **
をキーワードとして入力して<TAB>
、というのに最初は慣れなかったんですが、最近は 「fzf/peco を使ったオレオレ便利 function/alias キーバインドも使わないとすぐ忘れるから、**
に集約したほうが合理的」かも、と言うふうに思うようになってきました。
fzf の Fuzzy completion 機能は apt
等のパッケージマネージャ経由でインストールしている場合はバンドルされていない場合があるようです。その場合は install スクリプト を使っての再インストールを試してみてください。
fzf の Custom Fuzzy Completion を設定する
以下では基本的に zsh を前提にしています。bash でも大体は動くと思いますが一部追加の設定が必要だったりするので、詳しくは本家のドキュメントをご覧ください。
Custom fuzzy completion は実験的な機能なので、今後のアップデートで使い方が変わる可能性があります。
fzf の Custom fuzzy completion を設定するのはとっても簡単です。
README にある、架空の foo
コマンドに対する設定を見てみましょう。
_fzf_complete_foo() {
_fzf_complete --multi --reverse --header-lines=3 -- "$@" < <(
ls -al
)
}
_fzf_complete_foo_post() {
awk '{print $NF}'
}
このように_fzf_complete_<コマンド名>
と言う名前の関数をつくって .zshrc
に書いておけば、あとは fzf が zsh の機能を使って勝手に紐づけてくれます。
関数の中では例にあるような形で_fzf_complete
を呼び出します。
--multi --reverse --header-lines=3
の部分は無くてもOKで、任意の fzf
コマンドへのオプションを渡せます。
<( )
の部分が補完候補を生成する部分で、プロセス置換を使っています。任意のコマンドを実行して改行区切りで出力すれば OK です。
また、 _fzf_complete_<コマンド名>_post
は、↑の選択された候補から、実際に補完される文字列を取り出す処理です。
この設定を書くと、以下のような感じの動きになります。
見てもらえばわかる通り、ls -al
の結果を候補としてし表示されています。実際に補完されるファイル名だけでなく permission や ファイル容量等も同時に確認&絞り込みのフィルター文字列として使えるのが良いですね。
SORACOM CLI 向けの Custom Fuzzy Completion を設定する(シンプル)
というわけで書いてみたのが下記です。
いい感じじゃないでしょうか?
実際に入力したいのは UUID のグループ ID ですが、human readable なグループ名で絞り込めるのが good です。
下記に抜粋を載せて説明します。
コード全体は github に置いておいたのでそちらを参照してください。
_fzf_complete_soracom() {
if [[ "$@" =~ '--coverage-type *$' ]] then
_fzf_complete --no-sort --preview "echo hello {}" -- "$@" < <(_soracom_coverage_type_candidates )
elif [[ "$@" =~ '--imsi *$' ]] then
_fzf_complete --no-sort -- "$@" < <(_soracom_imsi_candidates )
else
eval "zle ${fzf_default_completion:-expand-or-complete}"
fi
}
_fzf_complete_soracom_post() {
awk '{print $1}'
}
function _soracom_coverage_type_candidates() {
echo 'g'
echo 'jp'
}
function _soracom_imsi_candidates() {
soracom subscribers list --fetch-all | jq -r '.[] | [.imsi, .tags.name] |@tsv'
}
簡単に解説すると、
_fzf_complete_soracom
関数では、 $@
に 入力中のコマンドライン文字列(から**
を取り除いたもの)が入っているので、 =~
を使って正規表現で入力中のオプションをマッチさせています。参考
マッチした先の関数では、 soracom subscribers list --fetch-all | jq -r '.[] | [.imsi, .tags.name] |@tsv'
のような形で SORACOM CLI の実行結果を jq
コマンドで tsv
形式に変換しています。
さらに _fzf_complete_soracom_post
関数で、選択された後に補完される値(空白区切りで最初の値)を抽出する、という流れです。
簡単ですね!
私のシェルスクリプト力はアレなので愚直な感じですが、やりたいことは素直に表現できていると思います。
SORACOM CLI 向けの Custom Fuzzy Completion をもっといい感じにする
実際にはこれまでのところで個人的な要件としては事足りているわけですが、改善アイデアが思い浮かぶと試してみたくなるのがエンジニアの性(個人の意見)です。
私が思ったのは、
- fzf って
--preview
オプションがイケてるから、使ってよりいい感じにしたいなー - 補完するたびに裏で API 呼びに行くのなんか嫌だなー、ローカルにキャッシュしたいなー
ということでした。
というわけで休日の貴重な自由時間を費やして、 zsh と戦いながら書いたのがこちらです。
良い。良いですね。何がというか個人的に「イケてる」感がでて満足です。
こちらでは、
- 補完用のCLI の実行結果(加工前) を
/tmp/soracom-value-complete-cache-imsi
のような tmp ファイルとして保存する - ↑のファイルを、TTL 5分のキャッシュとして使用する。(5分以内ならキャッシュファイルを使う、5分以上経過していたらもう一度 CLI を実行して新しい結果をとりにいく)
-
fzf
の preview に、選択されているアイテムのより詳細な情報(キャッシュファイルからフィルタした json)を表示する
というような機能を実現しています。
コードはこちらです
中身は見てもらえれば大体わかると思うのですが、1点私が今回調べていて読み解くのにすごく時間がかかったところだけ解説しておきます。
zsh の機能で TTL つきの簡易キャッシュ
_soracom_fzf_cache_valid() {
local cache_file_name="$1"
# declare an array
local -a ok
# check the last modification time is in 5 minutes
# See http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers
ok=( "$cache_file_name"(Nmm-5) )
# exit with 0 if valid cache exists (= num of array is 1), non-zero otherwise
(( $#ok ))
}
これは、zsh のマニュアル にあった例を改変したもので、引数で与えられたファイルが5分以内に更新されていれば true (=0) を返す関数なんですが、はじめ私のシェル力では何が起きているのか全然わからず、かつなんとググれば良いかもわからずに途方にくれました。
その後がんばって解読したので以下に説明します
-
local -a ok
は ok を配列として変数宣言 -
"$cache_file_name"(Nmm-5)
は zsh のファイル名展開機能の Glob Qualifier を使っていて、- N:
NULL_GLOB
をon = 展開結果がない場合に空文字列になるようにする - mm-5: は last modification time が 5分以内のファイルのみ選択
- 他にも ah-5 だと 「直近5時間にアクセスされたファイル」とか
- 他にも m+7 だと 「直近7日間に変更されていないファイル」とかが表現できる
- N:
-
ok=( "$cache_file_name"(Nmm-5) )
となっているのは array への代入で、すなわち
- 「5分以内に更新されたキャッシュファイルがある」場合は、キャシュファイル名の文字列を要素として含む配列
- そうでない場合は空配列 -
$#ok
は配列の要素数 -
(( $#ok ))
は Arithmetic Evaluation で、中身が non-zero の値として評価されると 0 を返す
というわけで完結に TTL つきのキャッシュを実現できてすごいんですが、
知らないと全くわからないので、よい子は利用する際にコメントを書いておきましょう。
Future Work
今回できていないところとして下記が思いついていますが、今必要ないのでエンハンスしたい気持ちをグッとこらえました。
もし、必要になって作った方がいらっしゃれば是非シェアさせてください。
- そもそも補完候補が足りていない
- 複数の coverage type を使うユースケースに対応していない
- SORACOM には jp と global の2つの coverage type があって、それぞれ異なるリソースを扱えるんですが、今回のキャッシュ機能はその区別をしていないです
- SIM とか Device とかを補完候補として、あるいは preview でみるときに、group の id だけでなく名前も表示したい(SIM 等の JSON データには group の id のみが含まれているけど、group の結果もキャッシュするようにしているのでそのキャシュファイルから引けば表示できるはず)
さいごに
fzf の custom fuzzy completion よい