Edited at

fishでのオプション解析の決定版!argparseコマンドの紹介

More than 1 year has passed since last update.


要約

fish 2.7.0でargparseという非常に便利な関数が入ったので,オプション解析にはこれを使おう!


fishでのこれまでのオプション解析

fish shellの関数でオプション解析するには,これまでは以下のような方法がありました.

getoptについては以下の記事でも触れられていますが,GNU実装とBSD実装に互換性がない問題があります.

bash によるオプション解析

この問題はfishプラグインでも同様で,fisherman/getoptsとoh-my-fish/plugin-getoptsはともにgetoptsというコマンドを提供しますが,コマンド名が被っているのに使い方に互換性がありませんでした(つまり,作った関数を他人に渡すと上手く動かない可能性がある).

その点,oh-my-fish/plugin-arguは名前被りもなく,使い方も分かりやすくて機能も申し分なかったのですが,ちょっとしたバグがあり特定のオプション1が解析できませんでした.

つまりどの方法にも難点があり,オプション解析の決定的な方法はありませんでした.

しかし,先月(2017/11/27)リリースされたfish2.7.0でオプション解析用の関数argparseがビルトインとして入りました.manを見ると「このコマンドはfishのスクリプトや関数が,fishのビルトイン関数が引数を扱うのと100%同じ方法で引数を取り扱うのを簡単にします.」とのこと.

『ビルトイン関数が引数を扱うのと100%同じ方法』で! いいですね.これからのオプション解析にはargparseを使うのがよさそうです.


argparseの使い方

詳細な使い方については,fishにmanが付属しています.自分用にmanを日本語訳したものをGistにあげているので,よければそちらも見てみてください.

ここでは,具体的な例を示しながらargparseの使い方を紹介します.


オプションを指定する

なにかすごいことをするコマンドsugoiを定義するとします.とりあえず-v(--version)でバージョン情報を表示するには,以下のようにします.


sugoi.fish

function sugoi

argparse -n sugoi 'v/version' -- $argv
or return 1

if set -lq _flag_version
echo "sugoi, version 0.0"
return
end

# ここで何かすごいことをする
end


argparseは不正なオプションを受け取った際のエラーメッセージ表示も自動でやってくれるので,エラーメッセージに表示されるコマンド名を-n(--name)オプションで指定します.

$ sugoi -z

sugoi: Unknown option '-z'
~/.config/fish/functions/sugoi.fish (line 3):
in function 'sugoi'
called on standard input
with parameter list '-z'

'v/version'はオプションの仕様で,-vおよび--versionが有効になります.sugoiコマンドに-vあるいは--versionオプションが指定されると_flag_vおよび_flag_versionというローカル変数が定義されます2.ここではset -lq _flag_versionで変数が定義されたか確認してバージョン情報を表示しています3

今は'v/version'オプションだけですが,オプション仕様はもちろん複数指定できます.オプション仕様を列挙したあと,--に続けて解析されるべき$argvを置きます.

--がオプション仕様と,解析される引数との区切りの役目をします.

argparseは解析に失敗すると非ゼロの終了ステータスを返すので,argparse文の後にor return 1を続けて関数を終了します.


ロングオプションだけを使う

先程は-vあるいは--versionでバージョン情報を表示しましたが,ロングオプションの--versionだけを使いたい場合はv/versionの代わりにv-versionとします.一応ショートオプションのvも書いておく不思議な書式です.


引数を取るオプションを使う

-o(--output)オプションで,sugoiコマンドの出力を指定したファイルに出力できるようにします.オプションの仕様に=を続けて'o/output='とするとオプションが引数を取るものと扱われます.


sugoi.fish

function sugoi

argparse -n sugoi 'v/version' 'o/output=' -- $argv
or return 1

if set -lq _flag_version
echo "sugoi, version 0.0"
return
end

set -lq _flag_output
or set -l _flag_output '/dev/stdout' # デフォルトでは標準出力に吐く

begin
echo "何かすごいこと"
end > $_flag_output

end


以下のように動きます.

$ sugoi -o file1.txt

$ cat file1.txt
何かすごいこと


オプションの値を複数保持する

先程定義したsugoiコマンドで,sugoi -o file1.txt -o file2.txtのように複数回-oオプションを使用すると最後に指定された値file2.txtだけが_flag_outputにセットされます.

これはこれで適切な振る舞いな気がしますが,ここでは景気良く-oで指定された全てのファイルに出力するようにしてみます.同じオプションで指定された値を全て保持するには,o/output=のかわりにo/output=+とします.


sugoi.fish

function sugoi

argparse -n sugoi 'v/version' 'o/output=+' -- $argv
or return 1

if set -lq _flag_version
echo "sugoi, version 0.0"
end

set -lq _flag_output
or set -l _flag_output '/dev/stdout' # デフォルトでは標準出力に吐く

for output in $_flag_output
begin
echo "何かすごいこと"
end > $output
end

end



同時に指定不可能なオプションを設定する

たとえば,出力をカンマ区切りにする-cオプション,タブ区切りにする-tオプション,空白区切りにする-wオプションを作るとします.これらのオプションはどれか一つしか設定できないはずです.argparseでは-x(--exclusive)オプションで,このようなオプション同士の排他的な関係を指定することができます.


sugoi.fish

function sugoi

argparse -n sugoi -x 'c,t,w' \
'v/version' 'o/output=+' 'c' 't' 'w' -- $argv
or return 1

# 以下省略


この状態で-c-tを同時に指定するとエラーになります.

$ sugoi -ct

sugoi: Mutually exclusive flags 'c' and `t` seen


おわりに

argparseでは他にも色々なことができて,オプション引数が整数値でなければエラーにする,といったこともできるのですが,長くなるので詳しくはmanを御覧ください.

オプション解析までビルトイン関数でできる! fishは本当にuser-friendlyなシェルですね!





  1. 具体的には,echoコマンドのオプションである-E, -e, -n, -sが解析されない. 



  2. 環境にローカル変数を定義するこの挙動はビルトインコマンドならではですね. 



  3. set -qでよさそうですが,それだとたまたまグローバルスコープに_flag_versionが存在する場合(どんな場合だ)にも反応してしまうので,ローカル変数にだけ反応するようにset -lqにしています.