getopts - コマンド "getopt", "getopts" の使い方を把握する
目的
"getopt"は、Cライブラリもコマンド版も、どちらも使い方を覚えにくい。
ここでは、コマンド版 "getopt" と、sh/bash built-in の "getopts" の使い方をまとめる。
結果
- sh/bash built-inのgetoptsが使える場合は、そちらを使った方が良い。
- 外部コマンド getopt 使用時は、クォート処理に気をつける。
- 速度的には、getoptとgetoptsどっちでも大して差はない。
- getopts.sh
- https://sssvn.jp/svn/spikelet/sh/getopts.sh
- getopt-o.sh
- https://sssvn.jp/svn/spikelet/sh/getopt-o.sh
以下、詳細。
getopt を使う(クォート考慮なし)
getopt(1)を参考にしつつ、素直に作成。
#!/bin/sh # sample code for getopt command. OPT=`getopt abc:d: $*` # calling getopt if [ $? != 0 ]; then # option error echo >&2 "usage: $0 [-ab] [-c arg1] [-d arg2] ..." exit 1 fi set -- $OPT # set result of getopt to arguments echo BEFORE: $* while [ $# -gt 0 ]; do case $1 in --) shift; break ;; -a) opt_a=true;; -b) opt_b=true;; -c) opt_c=$2; shift;; -d) opt_d=$2; shift;; esac shift done echo AFTER: opt_a=$opt_a, opt_b=$opt_b, opt_c=$opt_c, opt_d=$opt_d, rest=$*
getoptに渡す文字列は、Cライブラリのgetoptと同様に、
- 後置パラメータ無しならそのオプション文字のみ
- 後置パラメータ付き(必須)なら、文字+コロン
として、 その出力を "set --" を用いてコマンドライン引数に上書きすれば良い。
- 実行例1
- (一見成功)
% ./getopt.sh -a -d foo bar BEFORE: -a -d foo -- bar AFTER: opt_a=true, opt_b=, opt_c=, opt_d=foo, rest=bar % ./getopt.sh -l getopt: invalid option -- l usage: ./getopt.sh [-ab] [-c arg1] [-d arg2] ...
これで一件成功しているようだが、このスクリプトではクォート処理を無視している。
たとえば、以下のようにオプションを渡すと、誤ってパラメータが分割して渡される。
- 実行例2
- (foo と bar が分割されてしまう)
% ./getopt.sh -a -d "foo bar" baz BEFORE: -a -d foo -- bar baz AFTER: opt_a=true, opt_b=, opt_c=, opt_d=foo, rest=bar baz quux
getopt に -o オプション付加
getopt(1)の QUOTING に、この解決策のヒントが書いてある。
旧来のgetoptでは、クォートを一切考慮しない。これは、作成した引数を処理するshell側がどうクォートを理解するかわからないので、getoptがそれを気にしてもムダ、という理由からだそう。
これに対する拡張として、shellの書式でquoteした結果を吐き出す機能が、今のgetoptには付いている。
これを使うには、以下のようにすれば良い。
- getopt-o.sh
- https://sssvn.jp/svn/spikelet/sh/getopt-o.sh
#!/bin/sh # sample code for getopt command with -o option. unset GETOPT_COMPATIBLE OPT=`getopt -o abc:d: -- "$@"` # calling getopt if [ $? != 0 ]; then # option error echo >&2 "usage: $0 [-ab] [-c arg1] [-d arg2] ..." exit 1 fi eval set -- "$OPT" # set result of getopt to arguments echo BEFORE: "$@" while [ $# -gt 0 ]; do case $1 in --) shift; break ;; -a) opt_a=true;; -b) opt_b=true;; -c) opt_c=$2; shift;; -d) opt_d=$2; shift;; esac shift done echo AFTER: opt_a=$opt_a, opt_b=$opt_b, opt_c=$opt_c, opt_d=$opt_d, rest="$@"
- 実行例3
- (クォート処理も成功)
% ./getopt-o.sh -a -d "foo bar" baz quux BEFORE: -a -d foo bar -- baz quux AFTER: opt_a=true, opt_b=, opt_c=, opt_d=foo bar, rest=baz quux
先程との違いで、主な違いは以下:
- GETOPT_COMPATIBLE 変数を unset した
- getopt の書式変更 (-o オプション付加、$@ をクォートして渡す)
- set -- の書式変更 (変数はクォートして、eval 経由で実行)
(1)と(2)は、getopt にクォートを考慮させる指定。$*でなく$@にしたのは、クォートした時の形式が異なるため("$*"は引数全体をクォート、"$@"は個々の引数ごとにクォート)。
(3)は、getoptの出力がクォートされているので、それをshellが再度解釈するようにした。
この他に、getoptを実行しているshellの指定(-sオプション)もできるのだが、今どきcsh系列のシェルでscript書く人もいないだろうから、そこは無視する。
getopts を使う
shとbashのbuilt-inには、getoptsというものがある。
コマンドのgetoptと比べて、こちらの方が
- 中間文字列を作らずに、解析とオプション処理を一気に実行するので、わかりやすい
- クォート処理がデフォルトで入っているので気にする必要がない
という特徴があり、よりCのライブラリに近い間隔で使用できる。
また、エラーメッセージの行頭が "getopts" ではなくscript名になるので、script内で使いやすい。
- getopts.sh
- https://sssvn.jp/svn/spikelet/sh/getopts.sh
#!/bin/sh # sample code for getopts built-in. while getopts "abc:d:" flag; do case $flag in \?) OPT_ERROR=1; break;; a) opt_a=true;; b) opt_b=true;; c) opt_c="$OPTARG";; d) opt_d="$OPTARG";; esac done shift $(( $OPTIND - 1 )) if [ $OPT_ERROR ]; then # option error echo >&2 "usage: $0 [-ab] [-c arg1] [-d arg2] ..." exit 1 fi echo AFTER: opt_a=$opt_a, opt_b=$opt_b, opt_c=$opt_c, opt_d=$opt_d, rest="$@"
- 実行例4
- (クォートも正しく処理される)
% ./getopts.sh -a -d "foo bar" baz quux AFTER: opt_a=true, opt_b=, opt_c=, opt_d=foo bar, rest=baz quux % ./getopts.sh -l ./getopts.sh: illegal option -- l usage: ./getopts.sh [-ab] [-c arg1] [-d arg2] ...
ただ、気に入らない点が一つだけ。変数部分を削除するための shift の引数が冗長に見える。
Cのライブラリと仕様を合わせたのだろうが、shell scriptであればループを添字でまわすよりも、値を文字列に入れてまわす方が適している。であれば、shiftをしやすいように、OPTINDは最後に処理した引数の添字にして欲しかった。
処理速度の比較
getops は shell built-in なので、外部コマンドの getopt よりは処理が速いと期待される。
参考のために、処理時間を比較しておく。
掲載したscriptを1000実行した結果(10回平均)の比較:
条件 | script | 経過秒 | getoptsとの比較 |
---|---|---|---|
getopt(-o無し) | getopt.sh | 4.90 | 3.2%増加 |
getopt(-o有り) | getopt-o.sh | 5.04 | 6.1%増加 |
getopts | getopts.sh | 4.75 | - |
オプション解析という一度きりの処理で、数%しか差が出なかった(まあオプション文字列も単純だし、当然か)。特に速度面では getopts はそれほど優位ではない。