Shellscript コマンドライン引数のパースとサブコマンド(Bash
shellscriptでコマンドライン引数の解析とサブコマンド(Bashのみ) 前回の Shellscript コマンドライン引数のパース(Bash) を改良したサブコマンド版。
コード
#!/bin/bash COMMAND_USAGE=$( cat <<HELP Usage: $NAME [command] [options] [arguments] Command: $NAME add Add file contents to the index. $NAME commit Record changes to the repository. $NAME help Display this help and exit. $NAME version Output version information and exit. HELP ) readonly COMMAND_USAGE func_output_usage() { printf "\n%s\n\n" "$1" exit 2 } func_output_version() { echo "$NAME: $VERSION" exit 2 } func_output_error() { echo "Error: $1." 1>&2 exit 1 } # オプションの値をイコール(--option=value)で指定した場合の分割 func_split_by_equals() { IFS='=' read -ra ARRAY <<<"$1" OPTION="${ARRAY[0]}" if [[ -n "${ARRAY[1]}" ]]; then VALUE="${ARRAY[1]}" SKIP=false fi } # 正規表現を使って指定された長短のオプションが # 定義されているか確認して存在しなければエラーにする func_verify_option() { [[ "$1" =~ ^-([^-]+|$) ]] && [[ "$1" =~ ^-(.*[^$PATTERN_SHORT]+.*|)$ ]] && func_output_error "invalid option -- ‘$1‘" [[ "$1" =~ ^-{2,} ]] && [[ ! "$1" =~ ^-{2}($PATTERN_LONG)$ ]] && func_output_error "invalid option -- ‘$1‘" } # 値が必須なオプションに有効な値が # 指定してあるかチェックして無ければエラーにする func_verify_required_option_error() { [[ -z "$VALUE" ]] && func_output_error "required argument ‘$OPTION‘" "${SKIP}" && [[ "$VALUE" =~ ^-+ ]] && func_output_error "invalid argument ‘$VALUE‘ for ‘$OPTION‘" } ############################ # subcommands 1 ############################ # サブコマンド add のヘルプメッセージ SUBCMD_ADD_USAGE=$( cat <<HELP Usage: $NAME add [options] [arguments] Options: -h, --help Display this help and exit. HELP ) readonly SUBCMD_ADD_USAGE # サブコマンド add が使われた場合に # この関数内に処理を書く func_subcmd_add() { echo "No required : $O_NO_REQUIRED" echo "Optional : $O_OPTIONAL" echo "Required : $O_REQUIRED" echo "Length: $ARGC, (${ARGV[*]})" } # オプションを解析してその値をセットする func_subcmd_add_set_value() { # 値が必要ないオプションはこう書かく。 # 他のオプションを追加する場合は # これの n と --no-required と O_NO_REQUIRED の部分を書き換える if [[ "$OPTION" =~ ^(-[^-]*n|--no-required$) ]]; then O_NO_REQUIRED=true; fi # 値が必須なオプション # これも適宜 n と --no-required と O_NO_REQUIRED の部分を書き換えて追加または変更する if [[ "$OPTION" =~ ^(-[^-]*r|--required$) ]]; then func_verify_required_option_error O_REQUIRED="$VALUE" "${SKIP}" && SKIP_NEXT=true fi # 値が必須ではじゃないけど値をつけとることができるオプション # これも適宜以下同文 if [[ "$OPTION" =~ ^(-[^-]*o|--optional$) ]]; then O_OPTIONAL=true if [[ -n "$VALUE" ]]; then if "${SKIP}"; then if [[ ! "$VALUE" =~ ^-+ ]]; then O_OPTIONAL="$VALUE" "${SKIP}" && SKIP_NEXT=true fi else O_OPTIONAL="$VALUE" fi fi fi } ######################## # subcommands 2 ######################## # サブコマンド commit のヘルプメッセージ SUBCMD_COMMIT_USAGE=$( cat <<HELP Usage: $NAME commit [options] [arguments] Options: -h, --help Display this help and exit. HELP ) readonly SUBCMD_COMMIT_USAGE func_subcmd_commit() { echo "No required : $O_NO_REQUIRED" echo "Optional : $O_OPTIONAL" echo "Required : $O_REQUIRED" echo "Remaining arguments: Length $ARGC, (${ARGV[*]})" } func_subcmd_commit_set_value() { # No argument if [[ "$OPTION" =~ ^(-[^-]*n|--no-required$) ]]; then O_NO_REQUIRED=true; fi # Required argument if [[ "$OPTION" =~ ^(-[^-]*r|--required$) ]]; then func_verify_required_option_error O_REQUIRED="$VALUE" "${SKIP}" && SKIP_NEXT=true fi # Optional argument if [[ "$OPTION" =~ ^(-[^-]*o|--optional$) ]]; then O_OPTIONAL=true if [[ -n "$VALUE" ]]; then if "${SKIP}"; then if [[ ! "$VALUE" =~ ^-+ ]]; then O_OPTIONAL="$VALUE" "${SKIP}" && SKIP_NEXT=true fi else O_OPTIONAL="$VALUE" fi fi fi } func_subcmd_parse_arguments() { # オプションとその値以外の引数の数と配列 local -i ARGC=0 local -a ARGV=() local USAGE="" local PATTERN_SHORT="" local PATTERN_LONG="" case "$SUBCMD" in add) # サブコマンド add のヘルプメッセージをセットしておく USAGE="$SUBCMD_ADD_USAGE" # サブコマンド add で使うオプションの変数 local O_NO_REQUIRED="" local O_OPTIONAL="" local O_REQUIRED="" # サブコマンド add 用の正規表現 # func_verify_option 関数でチェックするときに使う # 設定の仕方は -a, --alpha というオプションを追加したければ # PATTERN_SHORT に "nora" と "a" を追加して # PATTERN_LONG に "no-required|optional|required|alpha" を追加 PATTERN_SHORT="nor" PATTERN_LONG="no-required|optional|required" ;; commit) # 上と同じ USAGE="$SUBCMD_COMMIT_USAGE" local O_NO_REQUIRED="" local O_OPTIONAL="" local O_REQUIRED="" PATTERN_SHORT="nor" PATTERN_LONG="no-required|optional|required" ;; esac while (($# > 0)); do case "$1" in -h | --help) func_output_usage "$USAGE" ;; -*) # イコールで区切られたオプション(--option=value)の場合は # shift の回数を減らす必要があるので # この変数が false のときはイコールで区切って # 値が指定されたことになる local SKIP=true # ここ書き始めて SKIP 変数の役割が紛らわしいことに # 気づいたから後で自分のテンプレートは変数名を変えることにする # SKIPはイコールで区切られてたかどうかで、 # 実際に shift を2回実行するのは SKIP_NEXT 変数が true のとき local SKIP_NEXT=false local OPTION="$1" local VALUE="$2" func_split_by_equals "$OPTION" func_verify_option "$OPTION" # サブコマンドに応じて値をセットする case "$SUBCMD" in add) func_subcmd_add_set_value ;; commit) func_subcmd_commit_set_value ;; esac # SKIP_NEXT が true の場合は shift を一回増やす "${SKIP_NEXT}" && shift shift ;; *) # 残りの引数の数(ARGC)をカウントして値を配列(ARGV)に入れる ((++ARGC)) ARGV+=("$1") shift ;; esac done # サブコマンドの実行 case "$SUBCMD" in add) func_subcmd_add ;; commit) func_subcmd_commit ;; esac } func_parse_arguments() { local SUBCMD="$1" shift case "$SUBCMD" in add | commit) func_subcmd_parse_arguments "$@" ;; version) func_output_version ;; *) func_output_usage "$COMMAND_USAGE" ;; esac } func_main() { local NAME=$(basename "$0") local VERSION="v0.0.1" func_parse_arguments "$@" } func_main "$@"
実行
$ command help Usage: command [command] [options] [arguments] Command: add Add file contents to the index. commit Record changes to the repository. help Display this help and exit. version Output version information and exit.
$ command add --help Usage: command add [options] [arguments] Options: -h, --help Display this help and exit.
$ command commit -r foo -n bar baz -o No required : true Optional : true Required : foo Remaining arguments: Length 2, (bar baz)
前の記事 から改良したとこは $ command -abc
の様に指定できるようにしたことと $ command --option=-100
の様にイコールを使って値を指定できるようにしたこと。このイコールを使った指定の何がいいのかというと -100
の様に -
から始まっていてもエラーにならない