はじめに
シェルスクリプトで数値判定(整数判定)を行う方法として expr
コマンドを使う方法が紹介されますが、これがうまく行かない例、つまりバグになる例を紹介します。
expr で数値判定を行う
expr
で数値判定を行うコードとしてよく見かけるのがこれです。数値ではない値を渡すとエラーになることを利用しています。
$ x = abc
$ expr "$x" + 1
expr: non-integer argument
$ echo $?
2
expr で数値判定を行うとバグになる例
expr
コマンドは計算結果が 0 だと偽 (0) を返し、終了ステータスはエラー扱いです。
$ expr "-1" + 1; echo "exit status: $?"
0
exit status: 1
GNU コマンドや Solaris では expr
コマンドの最初の引数が、length
、substr
、index
、match
という文字の場合に特殊な処理を行います。なお POSIX では最初の引数これらだった場合の動作は未指定となっており POSIX に準拠した動作です(というか移植性がないから未指定とせざるを得ない)
$ expr "length" + 1; echo "exit status: $?"
1
exit status: 0
正規表現を使った数値判定も考えられますが、length
という文字列だと正規表現比較になりません。match
を使って GNU 版コマンドとそれ以外で処理を分岐させればなんとかなるかな?
$ expr 'length' : '[0-9]*$'
expr: syntax error: unexpected argument ‘[0-9]*$’
--
はオプションと引数を区別するための引数です。ちなみに FreeBSD 版の expr
コマンドは -e
というオプションを持っています。
$ expr "--" + 1; echo "exit status: $?"
1
exit status: 0
$ # 補足 「--」 をちゃんと書こう
$ expr -- "--" + 1; echo "exit status: $?"
expr: non-integer argument
exit status: 2
さいごに
expr
コマンドを使って簡単に数値判定を行う方法があるかは不明です。expr
は &
(AND) とか |
(OR) とかカッコが使えるので長く書けばできそうな気はするんですけどね。正規表現を使う方法もありますが、expr
コマンドで使う正規表現は基本正規表現で、?
や +
は POSIX では使えない事になってるので注意してください。
正直な所、どちらにしろ expr
コマンドは外部コマンドで遅いので興味がないです。私はこんな感じの関数を使ってそれで判定しています。
is_number() {
case ${1:-} in
'' | *[!0-9]* ) return 1
esac
return 0
}
もしくは bash、ksh、zsh などなら [[ ... ]]
を使うと簡単ですね。
if [[ "$x" =~ ^-?[0-9]+$ ]]; then
: 数値
fi
test
コマンドが終了ステータス 2 を返すことを利用した判定方法もありますが、ksh(ksh93u+)、mksh、OpenBSD sh では動きません。ksh93u+m の POSIX モードなら動きます。あと何年かしたらこの方法を使えるようになるかもしれませんが、現時点の ksh は ksh93+ 止まりで ksh93u+m へ置き換わってはいません。
【macOS 13 などの場合】
$ ksh -c '[ str -eq 0 ]; echo $?'
1
【Solaris 11の場合(str変数を参照している、mkshやOpenBSD shも同様)】
$ ksh -c '[ str -eq 0 ]; echo $?'
0
【ksh93u+m(POSIXモード)の場合は判定できる】
$ ksh -c 'set -o posix; [ str -eq 0 ]; echo $?'
ksh: [: str: bad number
2
この記事は expr
コマンドで数値判定を行ってる例を見かけるけど、それ本当にちゃんと動く?と気になったので書いています。