bash: コマンドライン引数のオプション解析(optgets)


bashのコマンドライン引数の解析用ビルドインコマンドの覚書だ。

# サンプルソース基本編

コマンドを作成する際に利用される引数判定のための組み込み関数
-a などのオプションを認識させる。
以下が基本的なサンプルコードだ。

```bash:optgets_sample1.sh
#!/bin/bash -ue

# getoptsでの引数判定
while getopts ":hab:" OPT
do
  case $OPT in
    a) OPT_FLAG_a=1;;
    b) OPT_FLAG_b=1;OPT_VALUE_b=$OPTARG ;;
    h) echo  "Help";;
	:) echo  "[ERROR] Option argument is undefined.";;   # 
    \?) echo "[ERROR] Undefined options.";;
  esac
done

# getopts分の引数値移動
shift $(($OPTIND - 1))

# オプションの解析結果を表示
if [ -n "${OPT_FLAG_a+UNDEF}" ];then
  echo "opt=-a"
fi
if [[ -n "${OPT_FLAG_b+UNDEF}" ]];then
  echo "opt=-b, argv=${OPT_VALUE_b}"
fi

# オプションあとのコマンドライン引数を表示
for ((i=1; i <= $#; i++))
do
  eval echo argv[$i]=\$$i
done
```

# 説明

getopts関数に引数を指定し、OPT変数へ格納する。
引数について基本的なことを見ていく。

## オプション文字直後の「:(コロン)」について

オプション文字の直後にコロンをつけるかどうかで、オプションとしての動作が変わってくる。

### 文字の後に「:(コロン)」がない場合

オプションの後に引数を指定不可。

```bash
$ bash optgets_sample1.sh -a aaa bbb
opt=-a
argv[1]=aaa
argv[2]=bbb
```

### 文字の後に「:(コロン)」がある場合

オプションの後に引数を指定必須。

```bash
$ bash optgets_sample1.sh -b aaa bbb
opt=-b, argv=aaa
argv[1]=bbb
```

## 「:(コロン)」が先頭にある場合

未定義の引数が指定された場合のエラー処理を自前で行う。

### 「?」⇒未定義オプション指定時

未定義のオプションが指定された場合「?」を含む

```bash
$ bash optgets_sample1.sh -c
[ERROR] Undefined options.
```

### 「:」⇒引数必須オプションで引数未指定

定義済みのオプションで引数が未定義の場合「:」を含む

```bash
$ bash optgets_sample1.sh -b
[ERROR] Option argument is undefined.
```

## 「:(コロン)」が先頭にない場合

未定義の引数が指定された場合のエラー処理を自動で行う。
引数の部分だけを以下に変えてみれば試せる。

```bash
while getopts "hab:" OPT
```

### 未定義オプション指定時

```bash
$ bash optgets_sample2.sh -c
optgets_sample1.sh: 不正なオプションです -- c
[ERROR] Undefined options.
```

### 引数必須オプションで引数未指定

```bash
$ bash optgets_sample2.sh -b
optgets_sample1.sh: オプションには引数が必要です -- b
[ERROR] Undefined options.
```

# サンプルソース工夫編

工夫編と書いておきながらそんなに大したことはしない。
単純にいつも思っていたことだが、「OPT_FLAG_a」や「OPT_VALUE_b」をいちいち書くのが面倒だと考えていた。
どうせアルファベットのオプションしか使わないし、ロングオプションも許容していないのだから、ここら辺を便利にテンプレ化してみた。

```bash
#!/bin/bash -ue

# Usageを表示
function usage() {
  echo $1
  cat <<_EOT_
Usage:
  `basename $0` [-a] [-B] [-b bargv] ...

Description:
  XXX

Options:
  -a オプションa
  -B オプションB
  -b オプションbの引数値を指定
  -h ヘルプ表示

_EOT_
  exit 1
}

# オプションフラグ用変数初期化
function initOptionFlags() {
  for char in {{a..z},{A..Z}}
  do
    eval OPT_FLAG_${char}=0;
  done
}

# オプション解析結果を表示
function printOptions() {
  echo "### 指定オプション一覧"
  for char in {{a..z},{A..Z}}
  do
    opt=$(eval echo \${OPT_FLAG_${char}:-""})
    optarg=$(eval echo \${OPT_VALUE_${char}:-""})
    if [[ $opt = 1 ]];then
      echo "opt=-${char} ${optarg:+optarg=${optarg}}"
    fi
  done
  echo
}

# オプションフラグ用変数初期化(OPT_FLAG_[a-zA-Z])
initOptionFlags

# getoptsでの引数判定
while getopts ":aBb:h" OPT
do
  case $OPT in
    h) usage  "Help"; continue;;
    :) usage  "[ERROR] Option argument is undefined."; continue;;
    \?) usage "[ERROR] Undefined options."; continue;;
  esac
  # オプション解析
  eval OPT_FLAG_${OPT}=1;eval OPT_VALUE_${OPT}=${OPTARG:-""}
done

# getopts分の引数値移動
shift $(($OPTIND - 1))

# オプション表示
printOptions

# 引数の数を確認
echo "### 引数一覧"
for argv in $@
do
  echo ${argv}
done
echo

### main処理
echo "### Main処理"
# オプションaが指定されている場合
if [[ $OPT_FLAG_a = 1 ]];then
  echo "オプションaが指定されました。"
fi
# オプションBが指定されている場合
if [[ $OPT_FLAG_B = 1 ]];then
  echo "オプションBが指定されました。"
fi
# オプションbが指定されている場合
if [[ $OPT_FLAG_b = 1 ]];then
  echo "オプションbに${OPT_VALUE_b}が指定されました。"
fi
```

以下、実行結果。

```bash
$ ./optgets_sample.sh -B -a -b 1 1112
### 指定オプション一覧
opt=-a
opt=-b optarg=1
opt=-B

### 引数一覧
1112

### Main処理
オプションaが指定されました。
オプションBが指定されました。
オプションbに1が指定されました。
```


工夫してみたのは、以下の部分だ。

```bash
eval OPT_FLAG_${OPT}=1;eval OPT_VALUE_${OPT}=${OPTARG:-""}
```

OPT変数には解析オプション文字が入ってくる。
毎回手で変数名を指定せずに、evalを利用してOPTを展開してから変数を作成するようにしている。
evalを多用するのは読みにくくなる原因になってしまうが、基本はこれで行けると思う。

2018/02/13
結局オプションどう使うの？ってコメントいただいて、確かにわからん！って思ったので。
少し、ソース修正（余計な処理があったり、u指定で使いにくかった。。。）。
main処理で実際に使う際の処理例も書いてみたのでどうでしょう。

# 参考文献
[bash のシェルスクリプト（関数）でオプション引数を扱う getopts 使い方サンプル](http://takuya-1st.hatenablog.jp/entry/2015/12/24/234238)
[bash によるオプション解析](http://qiita.com/b4b4r07/items/dcd6be0bb9c9185475bb)
[bashオプション-uを付けた状態での未定義変数チェック方法](http://qiita.com/laikuaut/items/4bc07eabce56ee30812d)