【シェルスクリプト】オプションパーサーの紹介

シェルスクリプトはちょっとした処理を書くのには便利ですが、オプションや引数の制御をしっかり書こうとするとなかなか大変です。
今回は自分がよく利用するオプションパーサーの実装を紹介したいと思います。

# ■ 実装

```sample.sh
#!/bin/bash

function usage {
cat >&2 <<EOS
コマンドの説明

[usage]
 $0 <ARG1> <ARG2> [options]

[args]
 ARG1:
   第一引数
 ARG2:
   第二引数

[options]
 -h | --help:
   ヘルプを表示
 -k | --key:
  オプション名のみ指定するタイプのオプション
 -v | --key-value <VALUE>:
  値の指定が必要なオプション
 -r | --required <VALUE>:
  必須指定のオプション

[example]
  $0 hoge fuga -r piyo
EOS
exit 1
}

# errorレベルのエラーメッセージを出力しつつスクリプトを異常終了させる関数
function error {
  echo "[error] $1" >&2
  exit 1
}

# infoレベルのエラーメッセージを出力する関数
function info {
  echo "[info] $1"
}

# コマンドを表示しつつ実行する関数
function invoke {
  info $@
  $@ || error "コマンドの実行に失敗しました"
}

SCRIPT_DIR=$(cd $(dirname $0); pwd) # スクリプトが存在するディレクトリ(フルパス)
KEY=             # --keyオプションが指定されたかを格納する変数
KEY_VALUE=0      # --key-valueオプションの値を格納する変数
REQUIRED=0       # --requiredオプションの値を格納する変数

args=() # 引数(ARG1, ARG2)を格納する配列
while [ "$#" != 0 ]; do
  case $1 in
    -h | --help      ) usage;;
    -k | --key       ) KEY=1;;
    -v | --key-value ) shift; KEY_VALUE=$1;;
    -r | --required  ) shift; REQUIRED=$1 ;;
    -* | --*         ) error "$1 : 不正なオプションです" ;;
    *                ) args+=("$1");; # 引数を配列に追加します
  esac
  shift
done

# 引数がに過不足がある場合はヘルプを表示してエラー終了
[ "${#args[@]}" != 2 ] && usage

# 必須オプションが指定されていなければエラー終了
[ "${REQUIRED}" = "0" ] && error "-r | --required <VALUE> オプションが指定されていません"

# 値が必要なオプションに値が指定されていなければエラー終了
[ -z "${KEY_VALUE}" ] && error "-v | --key-value オプションに値が指定されていません"
[ -z "${REQUIRED}" ]  && error "-r | --required オプションに値が指定されていません"

ARG1="${args[0]}"
ARG2="${args[1]}"

# 処理に入る前にスクリプトディレクトリに移動
cd $SCRIPT_DIR
# 未定義の変数やコマンドのエラーでスクリプトを異常終了させる
set -eu

info "ARG1: ${ARG1}"
info "ARG2: ${ARG2}"
info "-k | --key: ${KEY}"
info "-v | --key-value: ${KEY_VALUE}"
info "-r | --required: ${REQUIRED}"

invoke id
```


# ■ 動作

1行ずつコードを解説していくのもどうかと思うので、どんな動作になるか見ていきたいと思います。

一応前提として

- 引数 ( `ARG1` , `ARG2` ) は必須指定。
- `-k | --key` オプションは任意指定で、値の指定は不要
- `-v | --key-value` オプションは任意指定で、値の指定が必要
- `-r | --required` オプションは必須指定で、値の指定が必要
- `-h | --help` はヘルプを表示するオプション


## ◎ 正常系

### # 通常実行
実行に成功すると、引数とオプションの値を表示します。

```
$ bash sample.sh a b --required hoge
[info] ARG1: a
[info] ARG2: b
[info] -k | --key:
[info] -v | --key-value: 0
[info] -r | --required: hoge
```

オプションは短縮名でも順番を入れ替えても問題ありません。

```
$ bash sample.sh foo bar -r hoge --key-value fuga -k
[info] ARG1: foo
[info] ARG2: bar
[info] -k | --key: 1
[info] -v | --key-value: fuga
[info] -r | --required: hoge
```

### # ヘルプの表示

```
$ bash sample.sh --help
######################################################################################################
# コマンドの説明
#
# [usage]
#  sample.sh <ARG1> <ARG2> [options]
... 略 ...
```

引数やオプションが指定されていても `-h | --help` が優先されます。

```
$ bash sample.sh a b -r hoge -h
######################################################################################################
# コマンドの説明
#
# [usage]
#  sample.sh <ARG1> <ARG2> [options]
... 略 ...
```

## ◎ 異常系
### # 引数が多い・足りない場合

ヘルプを表示してエラー終了します。

```
$ bash sample.sh a -r hoge
######################################################################################################
# コマンドの説明
... 略 ...
```

```
$ bash sample.sh a b c -r hoge
######################################################################################################
# コマンドの説明
... 略 ...
```

### # 必須のオプションが指定されていない場合

必須のオプションが指定されていない旨のメッセージを出力して、エラー終了します。

```
$ bash sample.sh a b
[error] -r | --required <VALUE> オプションが指定されていません
```

### # 存在しないオプションが指定された場合

メッセージを出力してエラー終了します。

```
$ bash sample.sh a b --hoge
[error] --hoge : 不正なオプションです
```

### # 値が必要なオプションに値が指定されていない場合

メッセージを出力してエラー終了します。

```
$ ./sample.sh a b -r hoge --key-value
[error] -v | --key-value オプションに値が指定されていません

$ ./sample.sh a b -r
[error] -r | --required オプションに値が指定されていません
```

<font color="red">※ 後ろにオプションが指定してあるとそれを値とみなしてしまいます</font>

```
$ ./sample.sh a b -r -k
[info] ARG1: a
[info] ARG2: b
[info] -k | --key:
[info] -v | --key-value: 0
[info] -r | --required: -k
```