shell scriptのoption取得では、画面に値が出たことと目的を満たしたことを分けて考えます。結論は「short optionはgetoptsで解析し、missing argumentとunknown optionを明示的に扱い、shift後のpositional argumentを検証します。long optionは別設計が必要です。」。Bash builtin getoptsは主にshort optionを解析し、呼び出しごとにOPTINDが進む条件のもと、出力の意味、境界値、影響のある操作を順に確認します。 確認ポイント:getoptsはshort optionを解析し、–helpは解析前の明示分岐でstdout・終了0に統一します。
受け付けるoptionと終了codeを仕様化する
読み取りqueryで正常時の件数とsampleを作ります。その値を上限として使い、急に対象が増えた場合は入力やpathの誤りとして停止できるようにします。
- 対応optionと必須argumentを仕様表にする
- unknown/missing時の終了codeを決める
- 同じshellで複数回parseするか確認する
- long optionとshort optionの範囲を分ける
getoptsで解析・検証・shiftする
基本optionを解析
usage() { printf '%s\n' 'usage: tool -f FILE [-n COUNT] [-v] [-h|--help]'; }
if [ "${1-}" = '--help' ]; then
usage
exit 0
fi
while getopts ':f:n:vh' opt; do
case $opt in
f) file=$OPTARG ;;
n) count=$OPTARG ;;
v) verbose=1 ;;
h) usage; exit 0 ;;
:) printf 'option -%s needs an argument\n' "$OPTARG" >&2; exit 2 ;;
\?) printf 'unknown option: -%s\n' "$OPTARG" >&2; exit 2 ;;
esac
done
shift "$((OPTIND - 1))"
先頭colonでerror処理をscript側へ寄せ、-hはgetopts、--helpは解析前の分岐で扱います。どちらもusageをstdoutへ出して終了0とし、不明optionと値不足だけをstderr・終了2にします。
必須値を検証
[ -n "$file" ] || { printf '%s\n' 'option -f is required' >&2; exit 2; }
if [ -n "${count-}" ]; then
case $count in *[!0-9]*) printf '%s\n' 'count must be an integer' >&2; exit 2;; esac
fi
-fは必須として非空を確認し、任意の-nは指定された場合だけ整数かを検証します。parse成功と値の妥当性を別々に確認します。
残り引数を確認
printf 'remaining=%s\n' "$#"
printf '%s\n' "$@"
shift後もquoteした展開で引数境界を保持します。
function内で再parse
parse_args() {
local OPTIND=1 opt
while getopts ':a:' opt "$@"; do
printf '%s=%s\n' "$opt" "$OPTARG"
done
}
同一shellで再利用する場合はOPTINDをscope内で初期化します。
helpを明示表示
./tool -h >help.out 2>help.err
printf 'short_help_rc=%d stdout_lines=%s stderr_bytes=%s\n' "$?" "$(wc -l <help.out)" "$(wc -c <help.err)"
./tool --help >/dev/null
printf 'long_help_rc=%d\n' "$?"
short helpとlong helpを実行し、stdoutのusage、空のstderr、終了0を別々に確認します。
OPTARG・OPTIND・残り引数を読み分ける
getoptsはoptstringを読み、option文字を指定variable、argumentをOPTARG、次のindexをOPTINDへ置きます。先頭colonの有無で診断動作が変わります。GNU getopt外部commandとは別で、long optionやoptional argumentのportable性を同一視できません。
処理時間とresource負荷も成功条件へ含め、large dataでの再現性を測ります。
正常・値不足・unknown・helpをtestする
正常、順序違い、combined short options、unknown、missing argument、leading dashの値、残り引数0/複数をtestします。stdout、stderr、終了code、shift後の引数を期待表と比較します。
復元testの結果も作業手順と同じ場所に残し、未検証のbackupを完了条件にしません。
getoptsと外部getoptを混同しない
- getoptsと外部getoptを混同する
- OPTINDを再利用時に初期化しない
- shiftをquoteせず引数境界を壊す
- missing argumentを空値として継続する
- long optionをportable getoptsで扱えると思う
権限不足を結果なしとして集計せず、error件数を独立して管理します。
option値をevalせず型検証する
option値をeval、unquoted path、SQL文字列へ直接連結しません。file path、整数、列挙値を型ごとに検証し、unknown optionで処理を続けません。help表示へsecretや受け取ったtokenをそのまま出さないようにします。
共有用の加工物から正本を復元しません。信頼できるbackupまたはversion管理のcommitを使います。
long optionは事前分岐か別parserで扱う
実例のうち「基本optionを解析」はbaselineを得る用途、「必須値を検証」は対象をさらに具体化する用途として使い分けます。両方の出力を同じ形式へ無理に整形せず、元の型と件数を保持したまま比較します。期待値は画面の見た目ではなく、対象ID、path、時刻など再照合できる列で定義します。
getopts の検証では、正常値、値不足、不明option、同じoptionの重複、-- 後の位置引数をそれぞれ試します。関数内で再度解析する場合は OPTIND の扱いを明示し、呼び出しごとに意図した先頭から始まるか確認してください。解析後の残り引数は shift $((OPTIND - 1)) 後も個別に引用し、空白やワイルドカードを含む値を一つの引数として保持します。
help表示はusage、利用できるoption、必須値、終了値を短くまとめます。不明optionや必須値不足では診断を標準エラーへ出して非0で終了し、-h や --help の正常表示は標準出力へ出す、という規則を先に決めます。自動testでは表示文だけでなく終了値と、残り位置引数が意図した順序で保持されることを確認します。
再試行の前に、shell builtinの getopts と外部 getopt を混同していないか、関数で再利用する OPTIND を初期化しているか確認します。shift $((OPTIND - 1)) の算術結果を使い、残り引数は "$@" で境界を保ちます。
必須値不足や不明optionを空値として継続せず、usageと非0終了値で停止します。passwordやtokenをcommand line optionへ渡すとprocess一覧や履歴へ残る場合があるため、秘密値の受け渡し方法は別に設計します。portable getoptsがlong optionへ対応すると仮定しません。
再現記録にはshellとscript version、optstring、入力したargvのfixture、OPTARG、解析後のOPTIND、残り引数、終了値を含めます。秘密値は架空値へ置き換えます。同じ関数を二回呼ぶtestでは、各回の初期化位置も記録します。
完了条件は、正常option、重複option、値不足、不明option、-- 後の位置引数が仕様どおり処理されることです。helpの正常終了と入力エラーの非0終了を分け、空白を含む引数が一つの値として残ることを確認します。
shell scriptのoption取得の開始記録には「対応optionと必須argumentを仕様表にする」を最初に置きます。続けて「unknown/missing時の終了codeを決める」を確認すると、対象違いと環境違いを作業前に分けられます。
対象範囲を確定する段階では「同じshellで複数回parseするか確認する」が判断材料になります。また「long optionとshort optionの範囲を分ける」を満たさない場合は、技術的に実行できても運用上の準備不足です。

コメント