bash で使えるコマンドラインオプションパーサとしては,組み込みの getopts とかコマンド getopt が代表的だが,もっと手軽に使えるのはないものか.ということで作ってみた.
細かいことは置いておいて*1,まず使用例を.
使用例1 †
#!/bin/bash
source ~/lib/bash/option-parser.sh
parse_opts "$@" # パース
optx=`opt x 10`
opty=`opt y hoge`
# ...
2~3行目がパースを行っている部分, `opt x 10` は -x というオプションの値を取得する関数(10 はデフォルト値).
./test.sh -x 100
を実行すると, `opt x 10` は 100 を返し,`opt y hoge` はデフォルトの hoge を返す.ちなみに `opt y` のようにデフォルト値を省略した場合は空白を返す. 単純.
使用例2 †
"-help" のような,単体で指定されるオプションもパースするには:
#!/bin/bash
source ~/lib/bash/option-parser.sh
set_single_opts help
parse_opts "$@" # パース
if [ `opt help 0` -eq 1 ]; then
echo "つかいかた!"
exit 1
fi
optx=`opt x 10`
opty=`opt y hoge`
# ...
3行目で,単体で指定されるオプションを指定している(複数でもOK).この場合,`opt help 0` はコマンドラインで指定されていれば 1 を,そうでなければ 0 を返す(デフォルト値の指定が無ければ空白).
./test.sh -x 100 -help
で「つかいかた!」と出力される.
使用例3 †
rm コマンドの "rm -i a.txt b.txt" みたいなオプションをパースするには:
#!/bin/bash
source ~/lib/bash/option-parser.sh
set_single_opts help i
parse_opts "$@" # パース
echo "number of fargs=${#fargs[@]}"
echo "fargs[0]=${fargs[0]}"
echo "fargs[1]=${fargs[1]}"
ハイフンなしのコマンドライン引数("rm -i a.txt b.txt" の a.txt や b.txt)は,すべて fargs という配列に格納される.よって []${#fargs[@]}[] で要素数の取得(2)が,[]${fargs[0]}[] で最初の要素が取得(a.txt)できる.もちろん,for ですべての要素に対する処理も可能だ:
for ((i=0;i<${#fargs[@]};i++)); do
echo "fargs[$i]=${fargs[$i]}"
done
マニュアル †
option-parser.sh スクリプトで解析できるのは
- single: -XX
- pair: -XX YY
- floating: YY
の3タイプのオプション.これらのオプションが,いくつでも,どんな順で並んでいてもよい.
まず,
source ~/lib/bash/option-parser.sh
set_single_opts AAA BBB
parse_opts "$@"
の順でパーサを実行する. set_single_opts は single (-XX) タイプのオプションを指定するコマンド(いくつでも指定可)で,指定しなければ pair (-XX YY) タイプのオプションとして認識されてしまう.
パーサを実行後,オプションの内容が以下のように取得できる.
- pair (-XX YY) オプションは,`opt XX` で YY を取り出せる.コマンドラインで指定が無い場合,空白
- single (-XX) オプションは,`opt XX` で 1 が返る.コマンドラインで指定が無い場合,空白
- floating (YY) オプションは,配列 fargs に,出現順に YY が格納される
opt コマンド(関数)は opt OPTION_NAME DEFAULT のようにデフォルト値 DEFAULT を指定できる.この場合,コマンドライン引数にオプションが無ければ opt 関数は DEFAULT を返す(pair でも single でも同じ).
NOTE: オプション名の記号はすべてアンダーライン(_)と同一視される.例えば -x+y と -x-y はいずれも内部で -x_y に置き換えられる.
NOTE: -- 以降はすべて fargs に格納される(i.e. floating (YY) オプションとみなされる).ただし,-- が pair (-XX YY) オプションの YY に位置する場合,オプション -XX の内容として扱われる.
スクリプトソース (option-parser.sh) †
- 修正@Oct.14,2009: 一部の echo を printf -- に置き換えた(cf. echo -E トラブル).
- 追加@Oct.14,2009: -- 以降はすべて fargs に格納されるようにした.
#!/bin/bash # コマンドラインオプション解析スクリプト # 解析できるオプションは以下の3タイプ: # single: -XX # pair: -XX YY # floating: YY # これらのオプションが,いくつでも,どんな順で並んでいてもよい # usage: # source ~/lib/bash/option-parser.sh # set_single_opts AAA BBB # single (-XX) オプションを指定 # parse_opts "$@" # パース # pair (-XX YY) オプションは,解析(parse_opts)後 `opt XX` で YY を取り出せる # single (-XX) オプションは, `opt XX` で 1 が返る (オプションで指定されていなければ空白) # single (-XX) のオプションを使う場合は, parse_opts を呼び出す前に set_single_opts を使う必要がある # floating (YY) オプションは,配列 fargs に YY が格納される # NOTE opt でコマンドライン引数にないオプションを取得しようとした場合 空白 "" が返る # NOTE オプション中の記号はすべてアンダーライン(_)と同一視される # NOTE -- 以降はすべて fargs に格納される(i.e. floating (YY) オプションとみなされる) # オプション名を内部名に変換する function option_name_conv() # OPTION_NAME { printf -- "$1" | sed 's/[^a-zA-Z0-9_]/_/g' } # OPTION_NAME の内容を取り出す # (何も格納されない場合は DEFAULT_VALUE が得られる) function opt() # OPTION_NAME [DEFAULT_VALUE] { eval "local res=\"\$commandline_option_`option_name_conv $1`\"" if [ "$res" == "" ] && [ "$2" != "" ]; then printf -- "$2"; fi printf -- "$res" } # 単体で使われるオプション(-XX タイプ)を指定する function set_single_opts() # OPTION_NAME [OPTION_NAME [OPTION_NAME] ...] { local N=$# for ((i=0; i<$N; i++)); do eval "commandline_option_single_`option_name_conv $1`=1" shift done } # 使用する変数を指定する function using_opts() # OPTION_NAME [OPTION_NAME [OPTION_NAME] ...] { local N=$# for ((i=0; i<$N; i++)); do eval "commandline_option_used_`option_name_conv $1`=1" shift done } # 使われていないオプション(-XX YY タイプのみ)の一覧を出力する # 使われていないオプションがあれば 1, なければ 0 を返す function check_unused_opts() { local unused_opt_exists=0 for on in ${commandline_option_list[@]};do eval "local used=\$commandline_option_used_`option_name_conv $on`" if [ $used -eq 0 ];then unused_opt_exists=1 eval "res=\"\$commandline_option_`option_name_conv $on`\"" echo "unused option: -$on $res" fi done return $unused_opt_exists } # すべての -XX YY タイプのオプションを出力 function list_options() { for on in ${commandline_option_list[@]};do eval "local res=\"\$commandline_option_`option_name_conv $on`\"" echo "$on=$res" done } function __register_opt() # optname optcontents { local optname=$1 local ioptname=`option_name_conv $1` local optcontents=$2 local duplicated=0 if [ $# -ne 2 ];then echo "invalid options for __register_opt! (bug!)"; fi eval "local oldcontents=\"\$commandline_option_$ioptname\"" if [ "$oldcontents" != "" ]; then echo "warning: option -$optname is redefined (note: internal name the option is -$ioptname)" duplicated=1 fi eval "commandline_option_$ioptname='$optcontents'" eval "commandline_option_used_$ioptname=0" if [ $duplicated -ne 1 ];then commandline_option_list[$commandline_option_count]=$optname commandline_option_count=$((commandline_option_count+1)) fi } function __register_fargs() # optcontents { if [ $# -ne 1 ];then echo "invalid options for __register_fargs! (bug!)"; fi fargs[$fargs_count]="$1" fargs_count=$((fargs_count+1)) } function parse_opts() { local optname='' local commandline_option_count=0 local fargs_count=0 local flag_ign_opt=0 local N=$# for ((i=0; i<$N; i++)); do if [ "$optname" == "" ];then if [ $flag_ign_opt -eq 1 ];then __register_fargs "$1" elif [ "$1" == "--" ];then flag_ign_opt=1 elif [ "`printf -- $1|sed 's/^\-.\+//g'`" == "" ];then optname="`printf -- $1|sed 's/^\-//g'`" eval "local is_single=\$commandline_option_single_`option_name_conv $optname`" if [ "$is_single" == "1" ];then __register_opt $optname 1 optname="" fi else __register_fargs "$1" fi else local optcontents=$1 __register_opt $optname "$optcontents" optname="" fi shift done if [ "$optname" != "" ];then echo "incomplete option: -$optname" exit 1 fi }
このソースのパス(上の例では ~/lib/bash/option-parser.sh)を source コマンドで呼び出して使う.