Quantcast
Channel: Bashタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 2810

シェルスクリプトで簡単にオプション解析ができるエレガントなオプションパーサー

$
0
0

以前書いた「高機能で短いシェルスクリプト用のオプション解析コード(POSIXシェル準拠・独自実装)」を元に機能強化しライブラリにしたオプションパーサー「getoptions」を作成しました。宣言的な定義関数を書くだけでオプション解析が行なえます。ループをぐるぐる回してオプションをチェックしていくコードはもう必要ありません。getopt → getopts → gnugetopt → getoptions と名前が長くなるほど高機能になっていると言えば覚えやすいと思います。(gnu-getoptは無理やりですが 笑)

bash 依存なしなので dash を含む全ての POSIX シェルに対応しており、外部コマンドもほぼ使ってない(catのみ使用)ので軽快に動作します。オプションの指定方法はロングオプションを含む一般的によく使われているもの(例 -abc -vvv -ovalue --long, --option=value, --等)であれば対応しています。(おそらく POSIXおよび GNUのオプションの仕様を満たせてるのではないかと。)またエラーメッセージを置き換えたりバリデーションや usage の自動生成機能もあります。

細かい使い方は README.md参照ですが、以下の例から直感的にわかるのではないかと思います。

#!/bin/shVERSION=1.0

. ./getoptions.sh

# オプションパーサーの定義関数
parser_definition(){
  setup plus:true --"Usage: ${2##*/} [options] [arguments...]"'''getoptions sample'''
  mesg --'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--"accept --param value / --param=value"
  param   NUMBER  -n--number   validate:'number "$1"'--"accept only numbers"
  option  OPTION  -o--option   default:"default"--"accept -ovalue / --option=value"
  disp    :usage  -h--help
  disp    VERSION       --version}

abort(){echo"$@">&2;exit 1;}
number(){case$OPTARGin(*[!0-9]*)
    abort "$1: not number"esac}eval"$(getoptions parser_definition parse "$0")"# parser 関数を元に parse 関数を定義eval"$(getoptions_help parser_definition usage "$0")"# parser 関数を元に usage 関数を定義
parse "$@"# オプション解析eval"set -- $RESTARGS"# オプション以外の残りの引数が $@ になる

上記の例は以下のようなオプションに対応させるためのコードです。

./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関数等)はコマンド置換($(...))のサブシェル内に閉じ込められるためグローバルを汚しません。また オプションパーサーで使用する変数は OPTARGOPTINDのみです。この2つの変数は本来 getoptsシェルビルトインコマンドで使われる変数ですが、getoptionsを使用するなら getoptsは不要となるはずなので再利用しています。

getoptions_help関数も同様に、usage関数を定義します。この usage の自動生成機能は簡易機能と考えてください。使い捨てのスクリプトでは十分だと思いますが、見やすい usageを作るのには適していません。出力が気に入らない場合はその他の方法で出力するなり改造するなりしてください。getoptions_help関数は getoptions関数と完全に独立しているので不要な場合は問題なく削除できます。(余談ですが usageの見やすさにこだわりたい場合は docoptsのような仕組みを追加したほうが良いと思います。)

おまけ: ksh93u のバグ

ksh93u で以下のコードがエラーになりハマった・・・(ksh93u 以前及び ksh2020 では問題なし)

#!/bin/shset-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」にしており(可能な限り)権利を放棄しているので、ライブラリとして使用したりスクリプト内にコピーしたり不要な部分を削除したり改造したりとライセンスのことを一切気にせず自由に使うことができます。

ということで私も自作ツールのオプション解析処理をこれに置き換えていこうと思います。


Viewing all articles
Browse latest Browse all 2810

Trending Articles