Bash で書かれたシェルスクリプトで引数を処理するためには、ビルトインコマンドの getopts
1が使用できますが、このコマンドは --foo
のようなロングオプションをサポートしていません。ロングオプションを GNU の getopt
を持たない macOS などの環境を含めてサポートしたい場合は自前で解析する方法や23、getopts
に -:
を渡して処理する方法45がありますが、この記事では後者の getopts
を使用した方法で、前述のリンク先の手法を参考にしながら、オプションの引数を受けてショートオプションとロングオプションを共通で扱う方法を考えてみます。
ロングオプションの引数
ロングオプションで引数を取るコマンド群には代表的なもので次の種類があります。
--foo=bar
: 区切り文字として =
を使って受け入れる
--foo=bar
のみを受け入れる実装は見当たりませんでした。
この記事の --foo=bar を処理する方法で扱っています。
--foo bar
: 引数を分けて受け入れる
FreeBSD tar、curl、Ruby、Python などで採用されている方式です。
この記事の --foo bar を処理する方法で扱っています。
--foo=bar
と --foo bar
の両方を受け入れる
GNU Coreutils、GNU Grep、Git、Node.js、GNU Awk など多くで採用されている方式です。
この記事の --foo=bar と --foo bar の 両方を処理する方法で扱っています。
-foo:bar
: 区切り文字として :
を使って受け入れる
Java で採用されている方式です。
-foo bar
: 引数を分けて受け入れる
getopts の機能
ここでは以下の基本的な関数を例にして、これをロングオプションに対応させてみます。
timestr() {
local opt h m s
while getopts h:m:s:v opt; do
case "$opt" in
h)
h="$OPTARG"
;;
m)
m="$OPTARG"
;;
s)
s="$OPTARG"
;;
v)
echo 'v0.0.0'
exit
;;
\?)
exit 1
;;
esac
done
shift $((OPTIND - 1))
local message="$1"
echo "$message $h:$m:$s"
}
getopts
は第一引数にオプション文字を並べてパースさせ、その結果が第二引数の変数に代入されるコマンドです。これを繰り返し呼び出していくことでオプションをパースしていきます。不明なオプションが指定されるとオプション名には ?
が代入されます。
引数を取るオプションは、h:
のようにしてオプションの文字のあとに :
を続けると $OPTARG
変数に引数が代入されます。上記の例では -h
、-m
、-s
の 3 つが引数を取るため、h:m:s:
と記述して引数を取得しています。引数を取らない -v
のようなオプションは単純に v
と記述します。
引数を処理し終わったら $OPTIND - 1
個分引数を shift
6 させることで、処理し終えた引数の続きから $1
, $2
, ... として使用することができます。
$ timestr -h 1 -m 23 -s 45 'Time is'
Time is 1:23:45
$ timestr -v
v0.0.0
$ timestr -d
timestr.bash: illegal option -- d
--foo=bar を処理する方法
区切り文字として =
を使って受け入れるタイプの処理方法です。
オプション文字として -:
を指定することで、その引数を利用してパースする仕組みです。
ここでは変数展開7を利用して =
の前後で分割した引数を $optarg
という変数に代入しています。
また --
を処理しているため、-
で始まる引数を渡すことができます。
timestr() {
local opt optarg h m s
while getopts h:m:s:v-: opt; do
# OPTARG を = の位置で分割して opt と optarg に代入
optarg="$OPTARG"
[[ "$opt" = - ]] &&
opt="-${OPTARG%%=*}" &&
optarg="${OPTARG/${OPTARG%%=*}/}" &&
optarg="${optarg#=}"
case "-$opt" in
-h|--hour)
h="$optarg"
;;
-m|--minute)
m="$optarg"
;;
-s|--second)
s="$optarg"
;;
-v|--version)
echo 'v0.0.0'
exit
;;
--)
break
;;
-\?)
exit 1
;;
--*)
echo "$0: illegal option -- ${opt##-}" >&2
exit 1
;;
esac
done
shift $((OPTIND - 1))
local message="$1"
echo "$message $h:$m:$s"
}
実行結果は以下のとおりです。
$ timestr -h 1 -m 23 --second=45 'Time is'
Time is 1:23:45
$ timestr --hour=1 --minute=23 --second=45 'Time is'
Time is 1:23:45
$ timestr --hour=1 --minute=23 --second=45 -- '-- Time --'
-- Time -- 1:23:45
$ timestr --version
v0.0.0
$ timestr --day
timestr.bash: illegal option -- day
--foo bar を処理する方法
引数を分けて受け入れるタイプの処理方法です。
こちらもオプション文字として -:
を指定していますが、ロング/ショートの両方で処理を統一するために、ショートオプションは引数の有無に関わらず引数なしとしてパースさせています。
オプションのうち引数を要求するものについては、$OPTIND
8 番目の変数から取り出して $optarg
に代入し、処理後に shift
6 を使用して引数をシフトさせます。
また --
を処理しているため、-
で始まる引数を渡すことができます。
timestr() {
local opt optarg h m s
# 引数を取る指定は - のみ
while getopts hmsv-: opt; do
# OPTIND 番目の引数を optarg へ代入
optarg="${!OPTIND}"
[[ "$opt" = - ]] && opt="-$OPTARG"
case "-$opt" in
-h|--hour)
h="$optarg"
shift
;;
-m|--minute)
m="$optarg"
shift
;;
-s|--second)
s="$optarg"
shift
;;
-v|--version)
echo 'v0.0.0'
exit
;;
--)
break
;;
-\?)
exit 1
;;
--*)
echo "$0: illegal option -- ${opt##-}" >&2
exit 1
;;
esac
done
shift $((OPTIND - 1))
local message="$1"
echo "$message $h:$m:$s"
}
実行結果は以下のとおりです。
$ timestr -h 1 -m 23 --second 45 'Time is'
Time is 1:23:45
$ timestr --hour 1 --minute 23 --second 45 'Time is'
Time is 1:23:45
$ timestr --hour 1 --minute 23 --second 45 -- '-- Time --'
-- Time -- 1:23:45
$ timestr --version
v0.0.0
$ timestr --day
timestr.bash: illegal option -- day
--foo=bar と --foo bar の両方を処理する方法
--foo=bar
と --foo bar
の方法を組み合わせた処理方法です。最もよく使われています。
こちらもオプション文字として -:
を指定し、次のようにパースします。
--foo=bar
の場合- オプション:
foo
、引数:bar
- オプション:
--foo bar
の 場合- オプション:
foo
、引数:bar
- オプション:
--foo=--bar
の場合- オプション:
foo
、引数:--bar
- オプション:
--foo --bar
の場合- オプション:
foo
、引数: なし - オプション:
bar
、引数: なし
- オプション:
ショートオプションにおいて引数を要求するかどうかは、getopts
の :
を指定するかどうかに依存します。一方でロングオプションにおいて引数を要求するかどうかは、各オプションの実装ではなくユーザーの入力に依存します。そのため shift
は不要です。こちらも --
を処理しているため、-
で始まる引数を渡すことができます。
timestr() {
local opt optarg h m s
while getopts h:m:s:v-: opt; do
# OPTARG を = の位置で分割して opt と optarg に代入
optarg="$OPTARG"
if [[ "$opt" = - ]]; then
opt="-${OPTARG%%=*}"
optarg="${OPTARG/${OPTARG%%=*}/}"
optarg="${optarg#=}"
if [[ -z "$optarg" ]] && [[ ! "${!OPTIND}" = -* ]]; then
optarg="${!OPTIND}"
shift
fi
fi
case "-$opt" in
-h|--hour)
h="$optarg"
;;
-m|--minute)
m="$optarg"
;;
-s|--second)
s="$optarg"
;;
-v|--version)
echo 'v0.0.0'
exit
;;
--)
break
;;
-\?)
exit 1
;;
--*)
echo "$0: illegal option -- ${opt##-}" >&2
exit 1
;;
esac
done
shift $((OPTIND - 1))
local message="$1"
echo "$message $h:$m:$s"
}
実行結果は以下のとおりです。
$ timestr -h 1 -m 23 --second=45 'Time is'
Time is 1:23:45
$ timestr -h 1 -m 23 --second 45 'Time is'
Time is 1:23:45
$ timestr --hour=1 --minute=23 --second=45 'Time is'
Time is 1:23:45
$ timestr --hour 1 --minute 23 --second 45 'Time is'
Time is 1:23:45
$ timestr --hour=1 --minute=23 --second=45 -- '-- Time --'
-- Time -- 1:23:45
$ timestr --hour 1 --minute 23 --second 45 -- '-- Time --'
-- Time -- 1:23:45
$ timestr --version
v0.0.0
$ timestr --day
timestr.bash: illegal option -- day
Bash Cheat Sheet
Bash のビルトインコマンドや各種コマンドがまとまったチートシート(websentra.com より)
脚注
- Bourne Shell Builtins (Bash Reference Manual)↩
- bash によるオプション解析 - Qiita(Wayback Machine)↩
- Bashでちょっと凝ったオプションの解析をする - Nekonote.↩
- shell scriptでlong optionを処理する - うしねずみの技術メモ↩
- bashでロングオプションとショートオプションの両方に対応する #Bash - Qiita↩
- Bourne Shell Builtins (Bash Reference Manual)↩
- Shell Parameter Expansion (Bash Reference Manual)↩
- Bourne Shell Variables (Bash Reference Manual)↩