Date 2021-06-12

ShellScript: set -u してるときにコマンド引数を扱う

※ この記事を書いてから1年以上経ってるかも

  tech ,  ShellScript

シェルスクリプト(Bash, Dash)を書くときに、 未定義変数への参照をエラーにする目的で set -u を設定すると思う。

そのときのアレ。

未定義エラー

何も考えずにコマンド引数を扱うスクリプトを書いてしまうと、 コマンド引数が与えられなかった場合に未定義エラーに遭遇しがち……。

#!/bin/sh
set -eu

echo "Arg[1] ... $1"

上記スクリプトを引数なしで実行すると、以下のようにエラーになる。

% /bin/bash sample.sh
sample.sh: line 4: $1: unbound variable

% /bin/ash sample.sh
sample.sh: 4: sample.sh: 1: parameter not set

部分抽出記法を使う

部分抽出記法の - を利用することで、この問題をうまく扱うことができる。

${parameter-word} とすることで、変数 $parameter が未定義(Null)の場合に、 文字列 word を代わりの値として使用できる。

また、代替文字列(先の例だと word)は空文字列でも大丈夫なので、 ${parameter-} と記述できる。
この場合、変数 $parameter が未定義の場合、代わりに空文字列が使用される。

これを利用し $1${1-} とするとで、未定義エラーを回避できる。

#!/bin/sh
set -eu

echo "Arg[1] ... ${1-}"

逆に言えば、 set -u のオプションでは、 部分抽出記法内に出てくる未定義変数はチェックされないということになるので、 そういった意味では注意が必要かも。

値が設定されているかを判定する

現実には、引数が渡されているかどうかで処理を分岐させることが多い。

多くの場合は "${1-}" が空文字列かどうかで判定するので十分(だと思う)。

#!/bin/bash
set -eu

if [ -n "${1-}" ]; then
    echo "Arg[1] ... $1"
else
    echo "Arg1 not set."
fi

上記の書き方では空文字列であるかどうかを判定しているだけであり、 厳密に未定義かどうかを判定しているわけではないので注意。

未定義か空文字列かを判定したい場合は、 以下のようにダミー文字列と比較するしかなさそう。

if [ "${var-___null___}" = "___null___" ]; then
    echo "var is probably an unbound variable."
fi

とはいえシェルスクリプトでそれを厳密に判定したいケースはほぼない気がする。

もしそういった判定が必要な処理を書くのであれば、 素直に Python や Ruby なりの利用を検討したほうが良さそう…… 🧐