はじめに
シェルスクリプトでオプション・引数解析といったらまず挙がるのが getopt
と getopts
です。さてどちらを使うべきでしょうか?始めに断っておくと実は私はどちらも積極的には使っていません。なぜなら独自実装でもほとんどコードは変わらず、より柔軟な処理ができるからです。とはいえ getopt
と getopts
はシェルスクリプトの基本なのでこれらの使い方について解説したいと思います。(解説が不要な人はそれぞれの「使用方法」を読んでください。)
本編の前に
この記事を書いたあと独自実装のオプション解析コードを書き最終的に getopt
や getopts
よりも高機能で使いやすいオプションパーサー getoptions を開発しました。もはやちまちまとしたコードを手書きする作業は不要です。シェルスクリプトで簡単に引数解析したいだけという方にはこちらをおすすめします。POSIXシェル準拠で getoptions
コマンドをインストールするだけで環境依存せずに使えますし、オプションパーサーを事前生成すれば getoptions
なしで動かすこともできます。詳細は以下のリンクから(時系列に関連記事を並べてます。基本的に最後の記事を見ればよいです。)
- [高機能で短いシェルスクリプト用のオプション解析コード(POSIXシェル準拠・独自実装)] (https://qiita.com/ko1nksm/items/7d37852b9fc581b1266e)
- 簡単に使えるエレガントなオプション解析ライブラリ(シェルスクリプト用)
- シェルスクリプト(bash等)の引数解析が究極的に簡単になりました
getopt
と getopts
の違い
getopt
と getopts
の違いですが、まず getopt
は外部コマンドで POSIX では規定されていません(関数としては規定されています (getopt)。それに対して getopts
はシェルビルトインコマンドで POSIX で規定されています (getopts)。そのためすべてのPOSIX準拠シェルで実装されている getopts
の方が移植性が高くなります。
また getopt
は大きく2種類存在しており、オリジナル版(BSD, macOS 版も同等)は最も機能が少なく GNU 版は高機能です。(このドキュメントでは macOS 版と GNU 版で検証しています。)
英語版 Wikipedia の getopts にはこのように書かれています。
getopts was first introduced in 1986 in the Bourne shell shipped with Unix SVR3.
getopts was developed as an improvement to the original getopt Unix program.
The original getopt program had fewer features than getopts.
An alternative to getopts is the GNU enhanced version of getopt.
つまり歴史的にみると以下のようになると思われます。(想像です)
- より少ない機能を持った
getopt
(オリジナル版) が作られた。 -
getopt
の機能を強化したgetopts
が作られた。 - GNU 版では
getopt
を拡張することでgetopts
よりも高機能になった。
(一方で macOS を 含む BSD 版は、オリジナル版と同等なので機能が少ない。)
それぞれの機能の違いの概略を以下に示します。
getopt | getopts | getopt (GNU版) | 補足 | |
---|---|---|---|---|
ショートオプション | Yes | Yes (POSIX) | Yes | 例 -x
|
結合されたショートオプション | Yes | Yes (POSIX) | Yes | 例 -xy
|
オプション引数 | Yes | Yes (POSIX) | Yes | 例 -x value , -xvalue
|
省略可能なオプション引数 | - | - | Yes | 例 -x or -x value
|
引数に空白を含めることができる | - | Yes (POSIX) | Yes |
"next day" のような引数 |
エラーメッセージの抑制 | Yes | Yes (POSIX) | Yes | |
エラーメッセージのカスタマイズ | - | Yes (POSIX) | - | |
ロングオプション | - | - | Yes | |
オプションとオプション以外の混在 | - | - | Yes | |
- で始まるロングオプション | - | - | Yes | 例 -exec
|
+ で始まるオプション | - | zsh, ksh, mksh | - | 例 +x
|
GNU 版 getopt
が最も高機能ですが、BSD や macOS を考慮する場合は、getopts
を使うしかありません。ただしロングオプションは使えません。
-
homebrew
でよければ GNU版getopt
であるgnu-getopt
がインストール可能です。 - 頑張れば
getopts
を使いつつロングオプションに対応することも出来るようですが、かなり無理した実装ばかりなので、それをやるぐらいなら独自実装の方が簡単です。
さてこれよりそれぞれのコマンドの詳細な使い方について解説したいと思いますが、歴史的な流れに従って「getopt
(オリジナル版)」「getopts
」「getopt
(GNU版)」の順で解説します。
getopt (オリジナル版)
まず GNU 拡張を含まない(おそらく)オリジナル版の getopt
の解説です。macOS を含む BSD 版の動作も同じです。オリジナル版の getopt
は引数に空白文字を含めることが出来ないという大きな欠点があるので推奨しません。移植性があり引数に空白文字を含めることができる getopts
の方が優れているので、オリジナル版を使う理由はないのですが getopts
と GNU 版 getopt
の前提知識として必要なため解説します。
getopt
は一般的にオプション解析をするためのコマンドと言われていますが、オプション解析をしやすくするために引数を整形するためのコマンドと捉えたほうが正確です。例えば -ab
のようなオプションを -a -b
に分離したり、-xvalue
のようなオプションを -x value
に分離したりします。
getopt
の使用方法は以下のとおりです。
getopt optstring parameters
第一引数の optstring
にオプションとして解析したい文字(このドキュメントでは「オプション文字列」と呼ぶことにします。)を指定します。parameters
が getopt
で整形させる引数です。
getopt
による出力(整形)の例を以下に示します。
呼び出し | 出力 |
---|---|
getopt abc -a -b -c |
-a -b -c -- |
getopt abc -abc |
-a -b -c -- |
getopt abc -a -b -c foo bar baz |
-a -b -c -- foo bar baz |
getopt a:bc -afoo -bc |
-a foo -b -c -- |
getopt abc -a foo -b bar -c |
-a -- foo -b bar -c |
getopt a:bc -a foo -b bar -c |
-a foo -b -- bar -c |
※ GNU 版では環境変数 POSIXLY_CORRECT
を定義すると同等の出力になります。
getopt の機能
ショートオプション
オプション文字列に a
と指定すると オプション -a
が使えるようになります。オプションとオプション以外は --
を挟んで前後に分割されます。
呼び出し | 出力 |
---|---|
getopt a -a |
-a -- |
getopt a |
-- |
getopt a foo |
-- foo |
getopt a -a foo |
-a -- foo |
結合されたショートオプション
(オプション引数を取らない)オプションは -abc
のようにまとめて書くことが出来ます。まとめて書いても getopt
が分解してくれるのでオプション解析が楽になります。
呼び出し | 出力 |
---|---|
getopt abc -abc |
-a -b -c -- |
オプション引数
オプション文字列の各文字の後に :
をつけると「オプション引数」を持つオプションという意味になります。オプション引数はオプションにつなげて指定する方法と、スペースで区切って次の引数で指定する方法があります。
呼び出し | 出力 | 補足 |
---|---|---|
getopt a: -a bc |
-a bc -- |
|
getopt a: -abc |
-a bc -- |
|
getopt a: -a -x |
-a -x -- |
この場合の -x は -a のオプション引数であってオプションではありません |
エラーメッセージ
不明なオプション、もしくはオプション引数がない場合は、エラーメッセージを出力し終了ステータスが非0で終了します。
呼び出し | エラー出力(例) |
---|---|
getopt a: -x |
getopt: 無効なオプション -- 'x' |
getopt a: -a |
getopt: オプションには引数が必要です -- 'a' |
オプション文字列の先頭に :
をつけるとエラーメッセージの出力を抑制することが出来ます。抑制するのはエラーメッセージの出力だけで、エラーが発生した時の終了ステータスは非0です。終了ステータスでエラーになったことはわかりますが、エラーとなったオプションや理由はわかりません。そのため getopt
を使用する場合はデフォルトのエラーメッセージを使用したほうが良いでしょう。
使用方法
位置パラメータへの設定
まずオプションを解析を行うために getopt
で整形した引数(出力)を位置パラメータ($1
, $2
...)に設定します。
args=$(getopt abc $*) || exit 1
# args=`getopt abc $*` 同等の意味の古い書き方
set -- $args
# eval "set -- $args" # zsh の shwordsplit に対応するにはこちらを使う
時折、以下のように変数に入れずにいきなり set
している例を見かけますが、これだと getopt
でエラーが発生しても set
呼び出しで正常終了になってしまうため間違った使い方です。(同様に for ... in
に直接渡すのもよくありません。)
set -- $(getopt abc $*)
ところで macOS (BSD) の man getopt
には以下のような記述があります。
you should not use `getopt abo: "$@"` since that would parse
the arguments differently from what the set command below does.
どうやら、getopt abc "$@"
としてはいけないようです。おそらく IFS
の値によって、set
コマンドと異なる解釈になるのを避けるためだと思うのですが納得できていません。getopt
コマンドは引数をスペース区切りでしか出力しないので、結局の所 IFS
はスペース区切り前提にするしか無いと思います。釈然としませんがオリジナル版の getopt
を使うことはないのでこれ以上追求しないことにします。理由をご存じの方はコメントで教えて下さい。
オプション解析
位置パラメータへ設定したら次はループを使ってオプションを解析していきます。ループには for
と while
がありますが、どちらを使っても殆ど変わりません。
まずは for
を使った例です。
# 位置パラメータへの設定
args=$(getopt a:b $*) || exit 1
set -- $args
# オプション解析のためのループ
for opt in "$@"; do # in "$@" を省略して for opt と書くことも出来ます。
case $opt in
-a) A_VALUE=$2; shift 2 ;;
-b) B_FLAG=1; shift ;;
--) shift; break ;;
esac
done
次に while
を使った例です。
# 位置パラメータへの設定 は 同じなので省略
# オプション解析のためのループ
while [ $# -gt 0 ]; do
case $1 in
-a) A_VALUE=$2; shift 2 ;;
-b) B_FLAG=1; shift ;;
--) shift; break ;;
esac
done
ループ変数 opt
を使用するか位置パラメータ $1
を使用するかの違いだけです。while
の場合に shift
が必要なのは当然として、なぜ for
でも shift
が必要なのでしょうか? それはオプション引数を参照するためです。for
を使って現在のオプションが opt
変数に代入されたとしても、オプションの次にあるオプション引数はわかりません。処理済みのオプションを shift
していくことで、オプション引数は常に位置パラメータ $2
から取得できるようになります。オプションは opt
変数、オプション引数は位置パラメータ $2
と違う方法で取得することになるので個人的には while
の方が好みです。もしオプション引数が必要ないなら shift
も必要なくなるので for
の方がわずかにシンプルになります。
getopts
getopt
が引数を整形するコマンドに対して、getopts
は呼び出すたびに一つずつオプションを読み取る関数です。オリジナル版の getopt
とは違い引数に空白文字が含まれていても問題ありません。解析したオプションは指定したシェル変数に代入され、オプション引数が存在する場合は OPTARG
変数に代入されます。全てのオプションの解析が終わると、getopts
は 終了ステータスとして非0を返し、OPTIND
変数にはオプション以外の残りの引数(オペランド)を処理できるよう、最初のオペランドのインデックス番号が代入されます。シェルビルトインコマンドとして実装されたことによりシェル変数を使うようになったのが大きな特徴で getopt
に比べてシンプルな使い方になっています。
getopts
による出力例を以下に示します。
呼び出し | OPT (実際は指定した名前) | OPTARG | OPTIND |
---|---|---|---|
getopts abc OPT -a -b -c |
1回目 a , 2回目 b , 3回目 c
|
4 | |
getopts abc OPT -abc |
1回目 a , 2回目 b , 3回目 c
|
2 | |
getopts abc OPT -a -b -c foo bar baz |
1回目 a , 2回目 b , 3回目 c
|
4 | |
getopts a:bc OPT -afoo -bc |
1回目 a , 2回目 b , 3回目 c
|
1回目 foo
|
3 |
getopts abc OPT -a foo -b bar -c |
1回目 a
|
2 | |
getopts a:bc OPT -a foo -b bar -c |
1回目 a , 2回目 b
|
1回目 foo
|
4 |
※ 全てのオプション解析が終わった時(= getopts
が非0を返した時)に OPT
変数に代入される値は POSIX 仕様では未定義のようです。殆どのシェルでは ?
になりますが zsh では最後に読み取ったオプションでした。
getopts の機能
getopts
の使用法方は以下のとおりです。
getopts optstring name [args]
オプション文字列の意味は、オリジナル版の getopt
と同じなので省略します。第二引数は変数名で読み取ったオプションが代入されます。args
は getopts
に解析させたい引数で省略された場合は現在の位置パラメータ "$@"
を指定したのと同じ意味になります。
※ getopts の仕様 によるとオプション文字列に使用できる文字は英数字のみで、その他の文字を使用した場合の結果は不定です。(The use of other option characters that are not alphanumeric produces unspecified results.
) getopts
でロングオプションに対応させる方法としてオプション文字列に -
を含めるやり方がありますが、POSIX 的には好ましくありません。
エラーメッセージ
getopts
では独自のエラーメッセージに変更することが可能です。エラーメッセージ抑制する方法は getopt
と同じくオプション文字列の先頭を :
にします。エラーが発生すると、エラーメッセージは抑制され (getopt
の場合とは違い)終了ステータスは 0
となります。発生したエラーが「不正なオプション」の場合は第二引数で指定した変数に ?
が代入され、OPTARG
変数に不正なオプション文字が代入されます。発生したエラーが「オプション引数がない」場合は第二引数で指定した変数に :
が代入されます。
呼び出し | OPT | OPTARG |
---|---|---|
getopts :a OPT -x |
? |
x |
getopts :a: OPT -a |
: |
※ bash では OPTERR
変数に 0
を設定してもエラーメッセージを抑制することができます。OPTERR
変数は POSIX の初期の提案でなされたものですが :
を指定する方法に置き換わり bash 以外は対応していません。
+ で始まるオプション
zsh, mksh, ksh では +
で始まるオプションを受け付けることが出来ますが、これは POSIX の仕様ではありません。
zsh と mksh では +
で始まるオプションを受け付けるために特にすることはありません。そのため、+
で始まる(オプションではない)引数が意図せずオプションとして扱われてしまう可能性があるので注意してください。ksh ではオプション文字列の先頭(エラーメッセージを抑制する :
がある場合はその次)を +
にします。
呼び出し | OPT (zsh, mksh) | OPT (ksh) |
---|---|---|
getopts ab OPT +b |
+b |
? |
getopts +ab OPT +b |
+b |
+b |
getopts +ab OPT ++ |
++ |
? |
OPTIND 詳細
getopts
は実行するたびにオプションを一つずつ処理していきます。つまりどこまで処理したかという状態を持っているわけです。その状態を持っている変数が OPTIND
変数です。OPTIND
変数に 1
を代入するとこの状態はリセットされ、新たなオプションを解析することができます。
さて以下のようなコードで、実行するたびに OPTIND
変数がどのように変わるか確認してみましょう。
echo "OPTIND:$OPTIND (start)"
set -- -a -bc -d data -e
while getopts abcd:e OPT; do
echo "OPT:$OPT OPTIND:$OPTIND"
done
echo "OPT:$OPT OPTIND:$OPTIND (end)"
行番号 | 出力 (dash, mksh) | 出力 (bash, ksh, posh) | 出力 (zsh) | 出力 (yash) |
---|---|---|---|---|
1 | OPTIND:1 (start) |
OPTIND:1 (start) |
OPTIND:1 (start) |
OPTIND:1 (start) |
2 | OPT:a OPTIND:2 |
OPT:a OPTIND:2 |
OPT:a OPTIND:1 |
OPT:a OPTIND:1:2 |
3 | OPT:b OPTIND:3 |
OPT:b OPTIND:2 |
OPT:b OPTIND:2 |
OPT:b OPTIND:2:2 |
4 | OPT:c OPTIND:3 |
OPT:c OPTIND:3 |
OPT:c OPTIND:2 |
OPT:c OPTIND:2:3 |
5 | OPT:d OPTIND:5 |
OPT:d OPTIND:5 |
OPT:d OPTIND:5 |
OPT:d OPTIND:5 |
6 | OPT:e OPTIND:6 |
OPT:e OPTIND:6 |
OPT:e OPTIND:5 |
OPT:e OPTIND:5:2 |
7 | OPT:? OPTIND:6 (end) |
OPT:? OPTIND:6 (end) |
OPT:e OPTIND:6 (end) |
OPT:? OPTIND:6 (end) |
なんとバラバラです。特に yash の出力はどういうことでしょうか?
OPTIND
変数の内容を次に処理すべきインデックス番号と仮定すると、-a -bc -d data -e
という引数の場合に、1番目の引数 -a
を処理するときは 1
ですが、さて2番目の引数のうち -c
を処理するときはどうなるべきでしょうか?
つまりショートオプションが結合されてる場合ではインデックス番号の数値一つだけでは足りないのです。そのため yash では :
区切りで二つの数値を OPTIND
変数に持っているわけです。他のシェルでは内部に見えない状態変数を持っているようです。
getopts によると、OPTIND
変数は、getopts
が非0で終了した時に、オプションを除くの最初の引数のインデックス番号になるとしか規定されてないようです。つまり途中の値がそれぞれ違っていても POSIX 仕様上は何の問題もなさそうです。(話がそれますが、zshの7行目の出力 OPT
の値は e
ですが POSIX ではオプション解析処理が終了したときに ?
にするとなってるので POSIX 準拠してないようです。)
さて、上の方で「OPTIND
変数に 1
を代入するとリセットされる。」と書きました。その検証をしてみます。
set -- -o -x # パターン1
# set -- -ox # パターン2
getopts ox OPT1
OPTIND=1
getopts ox OPT2
echo "$OPT1 $OPT2" # すべて o o と表示されるはず
パターン | 出力 (dash, bash, ksh, mksh, yash) | 出力 (zsh) | 出力 (posh) |
---|---|---|---|
パターン1 | o o |
o x |
o o |
パターン2 | o o |
o x |
o x |
zsh と posh ではリセットされませんでした。POSIX に準拠していないということでしょうか? zsh や posh では OPTIND
変数を unset
することで状態をリセットできましたが、unset
すると、dash
でエラーになりました。0
を代入すると yash でおかしくなります。どのシェルでも動く汎用的なリセット方法は見つからなかったのでシェル毎にリセット方法を変えるしかなさそうです。
使用方法
もっともシンプルな例はこんな感じでしょうか?
while getopts a:b OPT; do
case $OPT in
a) A_VALUE=$OPTARG ;;
b) B_FLAG=1 ;;
*) exit 1 ;; # 不正なオプション または オプション引数がない場合(OPT = ?)
esac
done
shift $((OPTIND - 1))
getopt
と比べて簡単になっている点は、事前の引数の整形が必要ないので位置パラメータへの設定が不要であるのと、オプション引数を飛ばす処理を getopts
が行うのでオプション毎の shift
が不要になっている点です。代わりにオプション解析でエラーが発生したときの中断処理と残りの引数を処理するための shift $((OPTIND - 1))
が増えています。
上記のコードは、オプション解析でエラーが発生した時のメッセージを getopts
にまかせていますが、メッセージをカスタマイズしたい場合は以下のようになります。エラーメッセージを抑制すると OPT
変数に入っている文字でエラーの内容を区別することができます。
while getopts :a:b OPT; do
case $OPT in
a) A_VALUE=$OPTARG ;;
b) B_FLAG=1 ;;
:) echo "Mission arg: -$OPTARG" >&2; exit 1 ;; # オプション引数がない (OPT = :)
*) echo "Unknown option: -$OPTARG" >&2; exit 1 ;; # 不正なオプション (OPT = ?)
esac
done
shift $((OPTIND - 1))
getopt (GNU版)
GNU 版の getopts
の使用法方は以下のとおりです。
getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters
1 はオリジナル版の getopt
に近い動きとなり、引数に空白や特殊文字が使えません。 2, 3 が GNU で拡張された動きになります。個人的には 2 は直感的でないと感じるので、3 をおすすめします。
GNU 版の getopt
による出力例を以下に示します。
呼び出し | 出力 |
---|---|
getopt -o abc -- -a -b -c |
-a -b -c -- |
getopt -o abc -- -abc |
-a -b -c -- |
getopt -o abc -- -a -b -c foo bar baz |
-a -b -c -- 'foo' 'bar' 'baz' |
getopt -o a:bc -- -afoo -bc |
-a 'foo' -b -c -- |
getopt -o abc -- -a foo -b bar -c |
-a -b -c -- 'foo' 'bar' |
getopt -o a:bc -- -a foo -b bar -c |
-a 'foo' -b -c -- 'bar' |
GNU版 getopt の機能
ロングオプション
-l
または --longoptions
でロングオプションを指定できます。ロングオプションはカンマ区切りで複数指定できます。ロングオプションだけを指定する場合でも、ショートオプションを指定する -o
または --options
は省略できないことに注意してください。
呼び出し | 出力 | 補足 |
---|---|---|
getopt -o '' -l long-a,long-b -- --long-a --long-b |
--long-a --long-b -- |
|
getopt -o '' -l reverse,recursive -- --rev |
--reverse -- |
オプション名は短縮指定できます |
ロングオプションのオプション引数
ショートオプションと同様に、オプション名の最後に :
をつけることで指定します。オプション引数はスペースで区切る他、オプションに =
でつないで渡します。
呼び出し | 出力 |
---|---|
getopt -o '' -l long-a: -- --long-a foo |
--long-a 'foo' -- |
getopt -o '' -l long-a: -- --long-a --long-a |
--long-a '--long-a' -- |
getopt -o '' -l long-a: -- --long-a=foo |
--long-a 'foo' -- |
省略可能なオプション引数
省略可能なオプション引数は、オプション名の最後に ::
をつけることで指定します。オプション引数はショートオプションにつなげて渡すか、ロングオプションに =
でつないで渡します。(スペースで区切って渡すことはできません。)
呼び出し | 出力 |
---|---|
getopt -o a::b -- -a -b |
-a '' -b -- |
getopt -o a::b -- -a foo -b |
-a '' -b -- 'foo' |
getopt -o a::b -- -afoo -b |
-a 'foo' -b -- |
getopt -o '' -l long-a::,long-b -- --long-a --long-b |
--long-a '' --long-b |
getopt -o '' -l long-a::,long-b -- --long-a foo --long-b |
--long-a '' --long-b -- 'foo' |
getopt -o '' -l long-a::,long-b -- --long-a=foo --long-b |
--long-a 'foo' --long-b -- |
オプションとオプション以外の混在
GNU 版の getopt
ではオプションとオプション以外を混在しても処理しやすい形に並び替えてくれます。途中でオプションとして --
見つかった場合はそこで並び替えを中断します。この機能を無効にしてオリジナル版の getopt
と同じ動きにするためには、オプション文字列の最初に +
を置くか、POSIXLY_CORRECT
変数を定義します。
オプション文字列の先頭を -
にすると、オプションでない引数は(--
の後ろのまとめられるのではなく)そのままの位置になります。(結合されたショートオプションの分解だけを行いたい時に使う?)
呼び出し | 出力 |
---|---|
getopt -o abc -- -a foo -b bar -c |
-a -b -c -- 'foo' 'bar' |
getopt -o +abc -- -a foo -b bar -c |
-a -- 'foo' '-b' 'bar' '-c' |
getopt -o -abc -- -a foo -b bar -c |
-a 'foo' -b 'bar' -c -- |
getopt -o abc -- -a foo -b -- bar -c |
-a -b -- 'foo' 'bar' '-c' |
getopt -o abc -- -abc foo -b bar -c |
-a -b -c -b -c -- 'foo' 'bar' |
getopt -o '' -l long -- --long=foo |
--long 'foo' -- |
単一の - で始まるロングオプション
-a
または --alternative
オプションを指定すると有効になります。find
コマンドで使われてる形式のオプションですね。(例 find . -name "*.txt"
)-
で始まるロングオプションは --
で始まるロングオプションに整形されます。
呼び出し | 出力 |
---|---|
getopt -o cetux -l exec -- -exec echo |
-e -x -e -c -- 'echo' |
getopt -o cetux -l exec -a -- -exec echo |
--exec -- 'echo' |
getopt -o cetux -l exec -a -- --exec echo |
--exec -- 'echo' |
getopt -o cetux -l exec -a -- -execute echo |
-e -x -e -c -u -t -e -- 'echo' |
getopt -o 'c:e:t:u:x:' -l exec -a -- -exec echo |
--exec -- 'echo' |
getopt -o 'c:e:t:u:x:' -l exec -a -- -execute echo |
-e 'xecute' -- 'echo' |
使用方法
ロングオプションなどの機能が追加されているだけで、使用方法はオリジナル版の getopt
と殆ど変わりません。getopts
に比べるとロングオプションが使えるなど機能は増えましたが、getopts
のシンプルさはなくなりました。
# 位置パラメータへの設定
args=$(getopt -o a:b -l long-a:,long-b -- "$@") || exit 1 # $@ を使うこと。$* では空白を正しく扱えない
eval "set -- $args" # オリジナル版と違いクォートでくくられるので eval する必要がある
# オプション解析のためのループ
while [ $# -gt 0 ]; do
case $1 in
-a | --long-a) A_VALUE=$2; shift 2 ;;
-b | --long-b) B_FLAG=1; shift ;;
--) shift; break ;;
esac
done
実際に書いてみるとすぐに気づくと思うのですが、ロングオプションの数が多くなると getopt
の引数は長くなり case
の部分とニ箇所に同じオプションを書かなければなりません。これは getopt
が引数を整形する機能しかないからです。実際の引数の解釈は別の case
で行わなければなりません。ショートオプションだけならまだマシですが、ロングオプションが増えてくると大変になってきます。
なお Ubuntu 18.04 の man getopt
より使用例が書かれたファイルがあることがわかるのですが、正しいパスは /usr/share/doc/util-linux/examples/getopt-parse.bash
(getopt-parse.tcsh
)でした。
例
(ba)sh と (t)csh での使用例のスクリプトは、 getopt(1) ディストリビューションで提供されている。
これらはオプションとして /usr/local/lib/getopt または /usr/lib/getopt にインストールされている。
まとめ
以上、getopt
と getopts
について徹底的に解説してみましたが、結局私は使わないんですよね・・・。以下が私の方針です。
- オリジナル版(BSD版)の
getopt
は使用しません。 - ショートオプションだけの簡単なものであれば
getopts
を使用します。 - ロングオプションやその他の機能が欲しくなったら独自実装します。
- GNU版の
getopt
は特定の環境だけで動けばよく簡易なものを手っ取り早く作りたいときには使うかもしれません。
独自実装のオプション解析コード
getopt
, getopts
を使用しない独自実装のオプション解析コードについては 「高機能で短いシェルスクリプト用のオプション解析コード(POSIXシェル準拠・独自実装) 」を参照してください。
関連リンク