shell scriptでlong optionを処理する

仕事でシェルスクリプトを時々書く。

オプション処理にはgetoptsを使っている。
オプションの種類が増えてくると
意味が分かりやすいロングオプションが使いたいくなるが
getoptsではショートオプションしか使えないらしい。

こちらの記事や
bash によるオプション解析
こちらの記事が
bashでロングオプションとショートオプションの両方に対応する
とても参考になり、やり方を考えてみた。

環境依存なgetoptはなるべく使いたくないし
自作せずに既存の方法をなるべく使って実現したくなる。

こんな感じかなぁ。。
ポイントはロングオプションが指定されたらoptとOPTARGを書き換えて
それ以降の処理をショート/ロングオプション共通にすること。

#!/bin/bash

check_arg_exit(){
	# オプションの引数がなかったら終了
	if [ -z "$OPTARG" ]; then
		echo "${0##*/}: option needs arg -- $opt"
		exit 1
	fi
}

while getopts "hf:-:" opt; do
	if [ $opt = "-" ]; then
		opt=`echo ${OPTARG} | awk -F'=' '{print $1}'`
		OPTARG=`echo ${OPTARG} | awk -F'=' '{print $2}'`
	fi

	case "$opt" in
		h | help)
			echo "help"
			exit 0
			;;
		f | file)
			check_arg_exit
			echo "target file name is ${OPTARG}."
			;;
		? )
			exit 1
			;; # ここはgetoptsのエラー出力に任せる
		* )
			echo "invalid option -- $opt"
			exit 1
			;; # 未定義のlong optionのエラーはここで出力
	esac
done
shift $((OPTIND - 1))

echo "main process"

exit 0

必須のオプション引数が指定されなかった時に
エラーを表示する方法がないので自前で「check_arg_exit」を定義しておき
必要なcase節に書いておく。

全部自前の「check_arg_exit」に統一しようと思って
getoptsのオプション指定の先頭にコロンを足して
エラー表示をしないようにしたのだけど

while getopts ":hf:-:" opt; do

これだとfオプションが引数無しで使われたときにcase文が?節に落ちる。
未定義のショートオプションを使った場合も?節に落ちるので
エラー表示を「オプション引数がない」にすればよいのか
「未定義のオプション」にすればいいのかわからないので諦めて
ショートオプションのエラー表示は全部getoptsに任せることにする。

(2016/10/16訂正)
man bashを読むとgetoptsは「silent」(オプション指定の先頭がコロン)なら
必須引数無は「:」、未定義オプションは「?」がoptにセットされるので区別可能。
「not silent」ならいずれも「?」がoptにセットされるらしい。
でもこのときoptが「:」や「?」になっていて
元のオプション文字はOPTARGに入っているので
自前のエラー処理と共通化できない。
ので、結局ショートオプションのエラー表示はgetoptsにお任せ。

未定義のロングオプションが指定されたら
エラー表示したいのでcase文の最後のアスタリスク節は必要。
その前の?節が無いと、未定義のショートオプションを指定した時に
getoptsからエラー表示が出てアスタリスク節でも自前のエラー表示も出てしまうので必要。

$ ./longopt.sh -a
./longopt.sh: 不正なオプションです -- a
invalid option -- ?

これでやりたいことは全部できてそう。
・ロングオプションを処理できる
・ロングオプションに引数指定できる
・未定義オプションでエラーを表示
・必須のオプション引数が無いときにエラー表示

あと出来てないのはこれぐらいだろうか。
私の用途ではこれらの要件は不要なので、もう満足です。
・通常引数の後にオプション指定
・引数があってもなくてもよいロングオプション

主な実行結果。

$ longopt.sh
main process

$ longopt.sh -h
help

$ longopt.sh --help
help

$ longopt.sh -f
/home/UserName/mysh/longopt.sh: オプションには引数が必要です -- f

$ longopt.sh -ffilename
target file name is filename.
main process

$ longopt.sh -f filename
target file name is filename.
main process

$ longopt.sh --file
longopt.sh: option needs arg -- file

$ longopt.sh --file=filename
target file name is filename.
main process

$ longopt.sh -a
/home/UserName/mysh/longopt.sh: 不正なオプションです -- a

$ longopt.sh --hoge
invalid option -- hoge