rcmdnk's blog

括弧の意味論

Bash/Zshではシェルの拡張を使って((i++))の様な 二重括弧を使って変数をインクリメント出来たりします。

ただこの実行結果の終了ステータスをきちんと考えないと面倒になる、という話。

二重括弧拡張

随分前に書いたのでちょっと適当な部分もありますが(言い訳)、 以下参照。

簡単に言うと、シェルスクリプトの中でiという変数に数字が入っていて それをインクリメントしたい場合

1
((i++))

とするだけで良いよ、という話。

exprを使って

1
i=$(expr $i + 1)

とする方法もありますが、 exprは遅い 1 ので、拡張機能を使えるならexprは使わないほうが良いです。

(())の終了ステータス

この二重括弧ですが、bashのマニュアルによると

((expression))
       The  expression  is  evaluated  according to the rules described
       below under ARITHMETIC EVALUATION.  If the value of the  expres-
       sion  is  non-zero, the return status is 0; otherwise the return
       status is 1.  This is exactly equivalent to let "expression".

となっていて、中身の評価値が0でない場合はstatusとして0を返し、 逆に0の場合は1を返す、という特徴を持つCompound Commands(複合コマンド)の一種です。

0がFalseで0以外がTrueというのは他の言語でよくある仕様だと思いますが、 その結果をシェル的に(0が正常終了、それ以外がエラー終了)返している感じです。

従って

$ ((0)); echo $?
1
$ ((1)); echo $?
0
$ ((-1)); echo $?
0

みたいな感じで中身が0の場合だけシェル的にエラー終了な状態になります。

((i++))の終了ステータス

ここで、次の様なシェルスクリプトを考えます。

ipp.sh
1
2
3
4
5
6
7
#!/usr/bin/env bash

i=-2
while [ $i -lt 1 ];do
  echo $i
  ((i++))
done

単に-2から0までを順に出力するだけのスクリプト。実行すると

$ ./ipp.sh
-2
-1
0
$

と、何事もなかったかの様に終了しますが、ここで終了ステータスを調べると

$ echo $?
1

とエラー終了になっています。

これは、最後に評価されるのが((i++))で、一番最後にはこのi0だからです。

i++によってi1に変わりますが、 後ろ++がある場合には評価されるときには変化する前のものが評価されます。 つまりこの場合は最後は((0))になるわけです。

これを

ipp.sh
1
2
3
4
5
6
7
#!/usr/bin/env bash

i=-2
while [ $i -lt 1 ];do
  echo $i
  ((++i))
done

の様に++iと置き換えてやれば最後の((++i))の評価はi変化後の((1)) となってスクリプトの終了ステータスは0になります。

どちらの場合もスクリプト自体は問題なく動きますが、 このあとに終了ステータスをチェックして何か行うようなことがあると 思わない状態になってしまいます。

((++i))の場合でも条件によっては同じことが起こります。

これを避けるためには

i=$((i+1))

の様に代入する様な形にするか、もしくは

ipp.sh
1
2
3
4
5
6
7
8
#!/usr/bin/env bash

i=-2
while [ $i -lt 1 ];do
  echo $i
  ((i++))
done
exit 0

の様に必ず最後にexit 0や関数であればreturn 0といった様に ステータスを含めて終了する様にすれば良いかと。

Sponsored Links
Sponsored Links

« 新しいGmailではフィルタの表示では順序が保持されないが中では順序保持されている? sd_cl: pecoやfzfなどにも対応したディレクトリ移動効率化ツール »

}