fishの略語展開で正確にサブコマンドを展開する

こんにちは。ATOM開発本部の古川です。 普段はATOMのAPIサーバやバッチ処理プログラムの開発を行っています。

皆さんは普段の開発でターミナルにgh pr checks -i 5 --watchdocker compose upなどの長いコマンドを打つのが面倒と感じたことはないでしょうか?

今回はターミナルでのコマンド入力を簡単に行うためのfishの略語展開機能とこの機能で正確にサブコマンドの展開を行う方法を紹介したいと思います。

fishの略語展開

まず、fishの略語展開について簡単に紹介します。

fishでは略語展開はビルトインでサポートされており、ビルトインコマンドabbrで略語を登録するだけで略語展開を行えます。

abbr -add g git
abbr -add gco git checkout
abbr -add ghw gh pr checks -i 5 --watch
abbr -a g git # 短縮形

例えば、上記のように登録するとg⎵gitgco⎵git checkoutにその場で展開されます。

登録した略語はスペースキーやエンターキーを押した際に展開されます。

デフォルトでは略語は行頭でしか展開されません。そのため、上記の例でgit g⎵と入力した場合でもgit gitにはならず、git gになります。

Fish 3.6.0以降は略語はセッション単位で保存されるため、略語を登録するコマンドはconfig.fishに書くことが推奨されています。

サブコマンドの略語展開

上記のように1単語の略語を登録しておいて、一発で目的のコマンドに展開するというのも便利ですが、個人的には略語を覚えやすくするために、d⎵i⎵b⎵と入力してdocker image buildに展開するなど、略語を1単語ずつ展開したいです。

このようなサブコマンドの略語展開は備え付けの--regex-r)オプションと--command-c)オプションを使って行えます。 例えば、下記のように登録するとc⎵docker c⎵では略語は展開されず、git c⎵と入力した場合にgit commitに展開されます。

abbr -a g_c  --regex c --command git commit

--regexオプションは略語名ではなく、指定した正規表現でマッチするかを判定するようにします。 略語名は重複させることができず、git c⎵ -> git commitdocker c⎵ -> docker containerなど同じ略語での別の略語展開を行うために使用する必要があります。

--commandオプションは略語を指定したコマンドの引数として使用されている場合にのみ展開するように設定します。 そのため、略語は行頭以外の任意の場所で展開されるようになります。

このように--regexオプションと--commandオプションを使えば簡単にサブコマンドの略語展開を実現できますが、この方法には以下の2つの問題があります。

  • 任意の場所で略語展開が行われるため、git commit c⎵git commit commitと展開されるようになる。
  • docker image b⎵ -> docker image buildなどのコマンドの階層が3階層ある場合に対応できない。
    • --commandオプションではスペースを含む文字列は指定できないため

これらの問題を回避してサブコマンドの略語展開を行うためには--function-f)オプションを使用したサブコマンドの展開を行う必要があります。

関数を用いたサブコマンドの略語展開

--functionオプションは略語が一致した際に呼び出される関数を指定するオプションです。 関数を呼び出した際は関数の終了ステータスが0の場合に略語を関数の出力結果に置き換え、終了ステータスが0以外の場合は展開を行わないません。

このオプションを利用して、関数でサブコマンドの略語展開を行うかをより厳密に判定できます。 関数はサブコマンドごとに新たに登録する必要があるので、私は以下のサブコマンドの略語を登録するabbr_subcommand関数を用意して利用しています。

function abbr_subcommand
    set -l main_command $argv[1]
    set -l abbreviation $argv[2]
    set -l expansion "$argv[3..-1]"

    set -l fn "_abbr_"$main_command"_"$abbreviation
    function "$fn" --inherit-variable main_command --inherit-variable abbreviation --inherit-variable expansion
        set -l cmd (commandline -op)
        if test (count $cmd) -eq 2 -a "$cmd[1]" = "$main_command" -a "$cmd[2]" = "$abbreviation"
            echo $expansion
            return 0
        end
        return 1
    end

    abbr -a "$fn" --regex $abbreviation --position anywhere --function "$fn"
end

abbr_subcommand関数の呼び出しは以下のようにします。

abbr -a g git
abbr_subcommand git a add
abbr_subcommand git c commit
abbr_subcommand git l log -n 15

この方法でサブコマンドの略語を登録するとgit c⎵ -> git commitの展開は行われ、git commit c⎵の展開は行われないようになります。

同様に--functionオプションと専用の登録関数を用いることでコマンドの階層が3階層ある場合にも対応できます。

3階層目のコマンドの略語を登録するabbr_subsubcommand関数は以下になります。

function abbr_subsubcommand
    set -l main_command $argv[1]
    set -l sub_command $argv[2]
    set -l abbreviation $argv[3]
    set -l expansion "$argv[4..-1]"

    set -l fn "_abbr_"$main_command"_"$sub_command"_"$abbreviation
    function "$fn" --inherit-variable main_command --inherit-variable sub_command --inherit-variable abbreviation --inherit-variable expansion
        set -l cmd (commandline -op)
        if test (count $cmd) -eq 3 -a "$cmd[1]" = "$main_command" -a "$cmd[2]" = "$sub_command" -a "$cmd[3]" = "$abbreviation"
            echo $expansion
            return 0
        end
        return 1
    end

    abbr -a "$fn" --regex $abbreviation --position anywhere --function "$fn"
end

abbr_subsubcommand関数の呼び出しは以下のようにします。

abbr_subsubcommand gh pr c create
abbr_subsubcommand gh pr co checkout
abbr_subsubcommand gh pr w checks -i 5 --watch

このように登録するとgh c⎵gh repo c⎵gh pr create c⎵で展開は行われず、gh pr c⎵でのみgh pr createへの展開が行われるようになります。

このようにabbr_subcommand関数やabbr_subsubcommand関数を用意するだけでfishのサブコマンドの略語展開が正確になりますので、皆さんもぜひ試して見てください。

参考