続編 → シェルスクリプト(bash等)の引数解析が究極的に簡単になりました
以前書いた「高機能で短いシェルスクリプト用のオプション解析コード(POSIXシェル準拠・独自実装)」を元に機能強化しライブラリにしたオプションパーサー「getoptions」を作成しました。宣言的なパーサー定義関数を書くだけでオプション解析が行なえます。ループをぐるぐる回してオプションをチェックしていくコードはもう必要ありません。getopt → getopts → gnugetopt → getoptions と名前が長くなるほど高機能になっていると言えば覚えやすいと思います。(gnu-getopt は無理やりですが 笑)
bash 依存なしなので dash を含む全ての POSIX シェルに対応しており、外部コマンドもほぼ使ってない(cat
のみ、オプション機能であるヘルプの自動生成で使用)ので軽快に動作します。オプションの指定方法はロングオプションを含む一般的によく使われているもの(例 -abc
, -vvv
, -ovalue
, --long
, --option=value
, --
等)であれば対応しています。(おそらく POSIX および GNU のオプションの仕様を満たせてるのではないかと。)またエラーメッセージを置き換えたりバリデーションやヘルプの自動生成機能もあります。2020-11-14追記 ロングオプション名を省略して指定できる機能と、サブコマンドへの対応を追加しました。
細かい使い方は README.md 参照ですが、以下の例から直感的にわかるのではないかと思います。
#!/bin/sh
VERSION=1.0
. ./getoptions.sh # もしくは中身を本体のスクリプトに貼り付け
# オプションパーサーの定義関数
parser_definition() {
setup RESTARGS plus:true error:error \
-- "Usage: ${2##*/} [options] [arguments...]" '' 'getoptions sample' ''
msg -- 'Options:'
flag FLAG_A -a -- "message a"
flag FLAG_B -b -- "message b"
flag FLAG_F -f +f --{no-}flag -- "expands to --flag and --no-flag"
flag VERBOSE -v --verbose counter:true init:=0 -- "e.g. -vvv is verbose level 3"
param PARAM -p --param -- "accepts --param value / --param=value"
param NUMBER -n --number validate:number -- "accepts only a number value"
option OPTION -o --option default:"default" -- "accepts -ovalue / --option=value"
disp :usage -h --help
disp VERSION --version
}
number() { case $OPTARG in (*[!0-9]*) return 1; esac; }
error() {
case $2 in
number) echo "option '$1' is not a number" ;;
*) return 1 ;; # その他はデフォルトのエラーを表示
esac
}
# parser_definition 関数を元に オプションパーサーである parse 関数を定義
eval "$(getoptions parser_definition parse "$0")"
# parser_definition 関数を元に ヘルプ表示用の usage 関数を定義(省略可)
eval "$(getoptions_help parser_definition usage "$0")"
parse "$@" # オプション解析
eval "set -- $RESTARGS" # オプション以外の引数を取得するために $@ を再設定
echo "$FLAG_A"
printf '%s\n' "$@" # オプション以外の残りの引数
上記の例は以下のようなオプションに対応させるためのコードです。
./sample/basic.sh -ab -f +f --flag --no-flag -vvv -p value -ovalue --option=value 1 2 -- 3 -f
細かい話
getoptions
関数は正確にはオプションパーサーのジェネレータです。getoptions
関数で生成したコードを eval
することでオプションパーサー関数(上記の例では parse
関数)を定義します。eval
せずに getoptions
関数単体で実行するとオプションパーサーのコードが出力されます。この生成されたコードのみを使用することも可能で、この場合は getoptions.sh
は不要となります。
getoptions.sh
がグローバルに定義する関数は getoptions
関数と getoptions_help
関数のみです。その他の関数(setup
関数や flag
関数等)はコマンド置換($(...)
)のサブシェル内に閉じ込められるためグローバルを汚しません。また オプションパーサーで使用する変数は OPTARG
と OPTIND
のみです。この2つの変数は本来 getopts
シェルビルトインコマンドで使われる変数ですが getoptions
を使用するなら getopts
は不要となるはずなので再利用しています。
getoptions_help
関数も同様に usage
関数を定義します。このヘルプの自動生成機能はオプション機能です。使い捨てのスクリプト程度であれば十分だと思いますが、出力が気に入らない場合はその他の方法で出力するなり改造するなりしてください。getoptions_help
関数は getoptions
関数と完全に独立しているので不要な場合は問題なく削除できます。(余談ですがヘルプの見やすさにこだわりたい場合は docopts のような仕組みを追加したほうが良いと思います。)
おまけ: ksh93u のバグ
ksh93u で以下のコードがエラーになりハマった・・・(ksh93u 以前及び ksh2020 では問題なし)
#!/bin/sh
set -eu 1 2
VAR='var'
while [ $# -gt 0 ]; do
[ $# -eq 1 ] && unset VAR # 最後のループで unset する
if [ "${VAR+x}" ]; then # VAR 変数が定義(set)されていたら?
echo "$VAR" # 定義されてないのに実行される => エラー「VAR: parameter not set」
fi
shift
done
[ "${VAR+x}" ]
で VAR
変数が定義されているときだけ echo
で変数を参照するはずなのですが unset
しても定義されていると誤判定します。もちろん実際には定義されてないためエラーになります。ワークアラウンドは eval '[ "${VAR+x}" ]'
とすれば OK です。eval
することで内部の何かがリセットされるのでしょう。
さいごに
似たようなツールはすでにあるのではないかと作る前にリサーチしましたが残念ながら見つかりませんでした。見つかったとしても bash 専用だったりしますし。bash 専用に目をつぶったとしても使い方や機能に満足がいくものはありませんでした。
ライセンスは「Creative Commons Zero v1.0 Universal」にしており(可能な限り)権利を放棄しているので、ライブラリとして使用したりスクリプト内にコピーしたり不要な部分を削除したり改造したりとライセンスのことを一切気にせず自由に使うことができます。
ということで私も自作ツールのオプション解析処理をこれに置き換えていこうと思います。