Bashでgetopts使って引数を解析する

# はじめに

getopts でできることをまとめておく。
ロングオプションはできないと書かれた記事があるけれど、できたよ。

# こんなことができたら素敵

```console
$ ./a.sh -h
usage: ./a.sh [OPTIONS...] ARGS...

options:
 -v, --verbose
 -h, --help
```

```console
$ ./a.sh
option_verbose: 0
```

```console
$ ./a.sh --verbose
option_verbose: 1
```

# コードサンプル1

```bash
#!/bin/bash

set -eu

usage_exit() {
  prog="./$(basename $0)"
  echo_err "usage: $prog [OPTIONS...] ARGS..."
  echo_err ""
  echo_err "options:"
  echo_err " -v, --verbose"
  echo_err " -h, --help"
  exit 1
}

echo_err() {
  echo "$1" 1>&2
}

echo_err_badopt() {
  local optarg="$1"
  echo_err "$0: illegal option -- $optarg"
}

option_verbose=0

while getopts -- "-:hv" OPT; do
  case $OPT in
    -)
      case $OPTARG in
        help) usage_exit;;
        verbose) option_verbose=$(($option_verbose+1));;
        *) echo_err_badopt $OPTARG; usage_exit;;
      esac;;
    h) usage_exit;;
    v) option_verbose=$(($option_verbose+1));;
    \?) usage_exit;;
  esac
done
shift $((OPTIND-1))

if [[ $# -ne 0 ]]; then
    usage_exit
fi

echo "option_verbose: $option_verbose"
exit 0
```

# コードサンプル2

先ほどのサンプルではもの足りない方はこちらを参照:

```bash
#!/bin/bash

set -eu

usage_exit() {
  prog="./$(basename $0)"
  echo_err "usage: $prog [OPTIONS...] ARGS..."
  echo_err ""
  echo_err "options:"
  echo_err " --dry-run           : Don't make any change (default)"
  echo_err " -f, --force         : Force mode"
  echo_err " -l ITEM --list ITEM : Set list items"
  echo_err " -V VAL --value VAL  : Set value"
  echo_err " -v, --verbose       : Verbose mode"
  exit 1
}

echo_err() {
  echo "$1" 1>&2
}

echo_err_needarg() {
  local optarg="$1"
  echo_err "$0: option requires an argument -- $optarg"
}

echo_err_badopt() {
  local optarg="$1"
  echo_err "$0: illegal option -- $optarg"
}

option_dry_run=yes
option_list=()
option_value=''
option_verbose=0

while getopts -- "-:fhl:V:v" OPT; do
  case $OPT in
    -)
      case $OPTARG in
        dry-run)
          option_dry_run=yes
          ;;
        force)
          option_dry_run=no
          ;;
        list)
          if [[ $OPTIND -gt $BASH_ARGC ]]; then
            echo_err_needarg $OPTARG
            usage_exit
          fi
          LONGOPT_ARG="${BASH_ARGV[$(($BASH_ARGC-$OPTIND))]}"
          option_list+=($LONGOPT_ARG)
          OPTIND=$((OPTIND+1))
          ;;
        value)
          if [[ $OPTIND -gt $BASH_ARGC ]]; then
            echo_err_needarg $OPTARG
            usage_exit
          fi
          LONGOPT_ARG="${BASH_ARGV[$(($BASH_ARGC-$OPTIND))]}"
          option_value=$LONGOPT_ARG
          OPTIND=$((OPTIND+1))
          ;;
        verbose)
          option_verbose=$(($option_verbose+1))
          ;;
        *)
          echo_err_badopt $OPTARG
          usage_exit
          ;;
      esac;;
    f) option_dry_run=no;;
    h) usage_exit;;
    l) option_list+=($OPTARG);;
    V) option_value=$OPTARG;;
    v) option_verbose=$(($option_verbose+1));;
    \?) usage_exit;;
  esac
done
shift $((OPTIND-1))

cat <<__JSON__
{
  "option_dry_run": "$option_dry_run",
  "option_value": "$option_value",
  "option_verbose": $option_verbose,
  "option_list": [$(
    if [[ ${#option_list[@]} -ne 0 ]]; then
      for e in ${option_list[@]}; do
        echo "\"$e\""
      done | paste -sd,
    fi
  )],
  "arguments": [$(
    i=0
    while [[ $# -gt 0 ]]; do
      echo "\"$1\""
      shift
    done | paste -sd,
  )]
}
__JSON__
```

# その他

正直、動くけどロングオプションで引数は受けたくない。
bashでやれなくないけどやるの？、って感じ。

# 参考資料

* https://github.com/kumarstack55/sandbox/tree/master/bash/getopts
* getopts の '-:' は stackoverflow の記事を参照したがリンクが見つかってない。
* [bash によるオプション解析
](https://qiita.com/b4b4r07/items/dcd6be0bb9c9185475bb)
  * getopt, getopts, 自前で解析の比較を試みた投稿。ただし getopts でロングオプション指定は不可としている: