【あるあるおハマり大事典】シェル関数の中でgetopts使っておはまりの巻

シェル関数が定義されているこんなファイルsourceme.shがありました:

# sourceme.sh
getopts_in_function_nogood() {
  echo ">>getopts_in_function_nogood"
  server=

  while getopts "ns:" opt; do
    case $opt in
      n) echo "dry run";;
      s) server=$OPTARG;;
      ?) echo "unknown option" 1>&2; return;;
    esac
  done
  shift $(($OPTIND - 1))

  echo "server=$server"
}

新しいターミナルを立ち上げて、ロードして関数を実行してみます。

goa[~]$ . sourceme.sh
goa[~]$ getopts_in_function_nogood -n -s web
>>getopts_in_function_nogood
dry run
server=web

期待通りの動作です。が、続けてもいっかい同じのを実行してみると…

goa[~]$ !!
getopts_in_function_nogood -n -s web
>>getopts_in_function_nogood
server=

なんということでしょう!!! オプションが全く解釈されてません!!!!! 同じなのに!! なぞい!!!

で、man bashを読んでみるとこんな記述が。。。

OPTIND はシェルまたはシェルスクリプトが呼び出される度に 1 に初期化されます。オプションが引き数を必要とする場合には、 getopts はその引き数を変 数OPTARG に格納します。シェルが OPTIND を自動的に再設定することはありません。 1 つのシェルが呼び出されている間に別のパラメータの組合せを使う場合には、 getopts の呼び出しの間に手動で再設定を行わなければなりません

(ꏿ⌓ꏿ)

ということで、シェル関数でgetoptsを使うときはOPTINDを明に初期化しましょう。

getopts_in_function_good() {
  echo ">>getopts_in_function_good"
  server=

  OPTIND_OLD=$OPTIND
  OPTIND=1
  while getopts "ns:" opt; do
    case $opt in
      n) echo "dry run";;
      s) server=$OPTARG;;
      ?) echo "unknown option" 1>&2; return;;
    esac
  done
  shift $(($OPTIND - 1))
  OPTIND=$OPTIND_OLD

  echo "server=$server"
}

というわけで早速マイyasnippetのsh-mode/getoptsを更新しましたとさ。めでたしめでたし。