理系学生日記

おまえはいつまで学生気分なのか

set -u した状態で変数に値が定義されているか否かの分岐を表現する

一般にシェルスクリプトでは set -eu しておけということを言われます。

-e はコマンド実行でエラーが発生した場合にそこでスクリプトの実行を終了する。 -u は誤って未定義変数を使用しようとしたときにエラーとする、という振る舞いになります。

shebang で bash 等を指定するときに、一緒に指定する方も多いのではないでしょうか。

#!/bin/bash -eu

ところが、最近 set -eu したスクリプトにおいて、これってどう表現したら良いのだろうということがありました。「変数に値が定義されているか否かで処理を分岐させたい」というケースです。

簡単に書けばこんなイメージでいけるはずですが、これを実行するとエラーになります。

#!/bin/bash -eu

if [ -n "${VAR}" ]; then
    :
fi

実行結果は以下の通りで、set -u の効果により unbound variable 、つまり変数が未定義としてエラーになっています。変数に値が定義済かどうかで判断したいのに、未定義として検知される…。

$ ./a.sh
./a.sh: line 3: VAR: unbound variable

もちろん一時的に set +u することで回避できますが、スマートな方法としては ${VAR-} として参照するようにすることが挙げられます。

--- b.sh        2020-08-22 13:40:39.000000000 +0900
+++ a.sh        2020-08-22 13:43:07.000000000 +0900
@@ -1,5 +1,5 @@
 #!/bin/bash -eu

-if [ -n "${VAR}" ]; then
+if [ -n "${VAR-}" ]; then
     :
 fi

${parameter-word} は、もし $VAR が未定義の場合は word として展開するという指定です。 このように指定すると、set -u した場合であっても問題なく値が定義されているか否かで判定できます。

そのほかの変数展開系については、こちらをご参照ください。

なお、${parameter-word}$parameter が未定義の場合のみ反応(wordに置き換え)します。 $parameter に空文字が設定されている場合は反応しません。 このあたりの詳しい挙動については以下が参考になります。