1文字オプション、ロングオプション、イコールを用いたロングオプション、後置き、中置きオプションをすべて考慮したgetoptsのすぐに使える使用例。
オプション例
例として次のようなオプションを受け入れることにする。
オプション指定 | キー | 引数 |
---|---|---|
-q / --quiet | quite | 無し |
-v / --verbose | verbose | 無し |
-qv | quite / verbose | 無し |
-ofilename / -o filename / --output filename / --output=filename | output | filename |
-ifilename / -i filename / --input filename / --input=filename | input | filename |
これでほぼすべてのケースを含んでいるはず。
スクリプト
上記のオプション例をパースするプログラム。
#!/usr/bin/env bash
while getopts ":-:qvo:i:" key; do
# part1
if [[ $key == - ]]; then
key=${OPTARG%%=*}
if [[ $key == "$OPTARG" ]]; then
keyarg=${!OPTIND}
else
keyarg=${OPTARG#*=}
fi
else
keyarg=$OPTARG
fi
# part2
case $key in
output | input)
if [[ $key == "$OPTARG" ]]; then
OPTIND=$((OPTIND + 1))
fi
;;&
q | quiet)
option_quiet=1
;;
v | verbose)
option_verbose=1
;;
o | output)
option_output=$keyarg
;;
i | input)
option_input=$keyarg
;;
? | *)
echo "help message"
exit 0
;;
esac
# part3
while [[ -n ${!OPTIND} && ${!OPTIND} != -* ]]; do
args+=(${!OPTIND})
OPTIND=$((OPTIND + 1))
done
done
set -- "${args[@]}"
動作テスト
テスト用に上記スクリプトの末尾に次を追加して動作させてみる。
echo "option_quiet:$option_quiet"
echo "option_verbose:$option_verbose"
echo "option_output:$option_output"
echo "option_input:$option_input"
echo "args:$@"
すべてのパターンを網羅はできていないが適当に動かしてみる。
./getopts.sh -qv -ifilename -ofilename a b c
option_quiet:1
option_verbose:1
option_output:filename
option_input:filename
args:a b c
./getopts.sh -i file1 -o file2 a b c --quiet --verbose d e
option_quiet:1
option_verbose:1
option_output:file2
option_input:file1
args:a b c d e
./getopts.sh --input=file1 a b c --quiet --verbose --output=file2 a c
option_quiet:1
option_verbose:1
option_output:file2
option_input:file1
args:a b c a c
想定通りに動いていることが確認できる。
解説
本スクリプトを応用して別なオプションを受け入れるよう改変を加えるために、説明が必要と思われる部分について解説していく。
getopts
getoptsの第1引数は":-:qvo:i:"
となっているが、これは基本的にアルファベット1文字+コロンの有無を必要回数だけ続けるというルールになっている。この場合は分解すると、
- 先頭コロンあり
- ハイフン+コロンあり
- q+コロンなし
- v+コロンなし
- o+コロンあり
- i+コロンあり
となる。先頭コロンなしの場合、未知のオプションキーに遭遇した際にgetopts側でエラーが表示される。本スクリプトは未知のオプション遭遇時にはヘルプメッセージを表示することを想定しているため行頭コロンありとしている。
以降は機械的に「アルファベット1文字+必要ならコロン」の組み合わせをつなげるだけである。コロンありはそのオプションに引数があることを意味する。「ハイフン+コロンあり」はロングオプションを処理するために必要で、そうでなければ不要である。
本スクリプトではoキーとiキーは1文字キーとロングオプションの2通りがあるがinput/outputはロングオプションのみを受け付けるとした場合はgetoptsの第1引数での指定は不要である(ハイフンで処理される)。
getoptsはあくまで1文字キーのオプションを処理してくれるもの。今回の例だと、-qvはqキーとvキーだが、-iqvはiキーの引数qvである、などの解釈をそれなりにやってくれるため自前のパースよりは効率が良い。
whileのpart1
if [[ $key == - ]]; then
key=${OPTARG%%=*}
if [[ $key == "$OPTARG" ]]; then
keyarg=${!OPTIND}
else
keyarg=${OPTARG#*=}
fi
else
keyarg=$OPTARG
fi
この部分はロングオプションを受け付けない場合は単にkeyarg=$OPTARG
とすればいい(直接$OPTARG
を参照するなら何もなくていい)。ロングオプションの場合はオプションキー$key
がハイフンの時であるのでそこだけ場合分けをする。イコールによるロングオプションを受け付けない場合は、key=${OPTARG}
、keyarg=${!OPTIND}
のみでよい。
さらなる場合分けでイコールによるロングオプションを処理している。イコールによるロングオプションでは$OPTARG
にoutput=filename
といった文字列が格納されているのでこれをイコールでkeyとkeyargに分割する。
key=${OPTARG%%=*}
keyarg=${OPTARG#*=}
これはシェル展開で後方からイコールが現れるまでをカット(最長一致)、前方からイコールが現れるまでをカット(最短一致)しており、結果としてそれぞれにイコールの前部分と後ろ部分が格納される。
イコールが含まれていない場合は$key=$OPTARG
となるのでそこで場合分けをしている。
whileのpart2
ここでは特筆すべきところは以下。
output | input)
if [[ $key == "$OPTARG" ]]; then
OPTIND=$((OPTIND + 1))
fi
;;&
イコールを用いないロングオプションを使用しないならこの部分はまるまる不要である。
そうでない場合、caseの冒頭ですべてのロングオプションを拾うような項目を用意する必要がある。イコールを用いないロングオプションでは${!OPTIND}
をkeyargのために消費しているので次に処理されるオプションのインデックスである$OPTIND
に1加算しておく必要がある。イコールにの有無による場合分けが例によって必要になる。
その他の箇所は極めて自然だろう。ここに関する例は一般的なgetoptsの解説でもよく見ることがある。
caseの最後はbreakを意味する;;
ではなく、次にマッチするcaseを処理せよ、を意味する;;&
なことは注意。
whileのpart3
後置きと中置きオプションを考慮しないならこの部分はまるまる不要である。
getoptsは$OPTIND
がで表される次に処理すべき引数がハイフンから始まっていないとそこで処理を終了してしまうため、通常のオプションをargsに貯めながら中置き・後置きのオプションのある所まで$OPTIND
を進めておく。これによって、中置き・後置きのオプションが次のwhileでgetoptsによって処理される。
whileのあと
$args
に通常の引数が貯められている。set --
であたかも普通の引数のようにしてその後のプログラムを書いてもよいし直接$args
を見てもいい。後置き・中置きオプションを考慮しない場合(part3をまるまるカットした場合)はset --
したときと同じ動作はshift $((OPTIND - 1))
で達成できる。