LoginSignup
148
138

Bash $((算術式)) のすべて - A 基本編

Last updated at Posted at 2016-12-04

Bash の算術式の基本について詳細に解説します!
※この記事は AdC 2016 Shell Script 4日目 Bash $((算術式)) のすべて - Qiita の衛星記事です (が、実のところこちらの記事のほうが実用性が高いような気がします…)。

関連記事一覧:

記事の構成:

先ず A1 で算術式が何かについて述べ、A2 でBash算術式に登場する型を紹介します(一つしかありませんが)。A3 で算術式を構成する原子式・演算子などについて説明をします。A4 で Bash 算術式が使える箇所を列挙します。まとめについては親記事の §1.2, §1.3, §1.4 を参照して下さい。

A1. 算術式とは?

シェルの算術式 (arithmetic expression) とは、シェルの中で整数 (など) の計算を行うために記述する数式のことです。例えば、いちばん知られているのは $(( ~ )) という形で記述する算術式展開 (arithmetic expansion) です。中の ~ の部分に算術式を記述します。算術式展開を含むコマンドを評価すると、$(( ~ )) が中の算術式の評価結果に置き換えられます。

例えば、以下のようなスクリプトを考えます。

スクリプト
i=1234
echo $((i*2))

2行目のコマンドの評価時には、まず算術式展開が実行されて

Bash内部 (敢えて書くなら)
echo 2468

という形に置換されてからコマンドが実行されます。結果として出力は以下のようになります。

結果 (標準出力)
2468

算術式自体は POSIX sh でも定義され、bash, zsh, ksh, yash, dash を始めとして多くのシェルに実装されていますが、ここでは特に Bash の算術式に絞って説明をすることにします。

A2. Bash の算術式の型

内部的には intmax_t (C言語) が使用されています。殆どのシステム上では 64 bit 符号付き整数になるかと思います。シェルによっては浮動小数点数を扱えるもの (Zsh など) もありますが、Bash では整数しか扱えません。

A3. Bash の算術式で使える式

算術式は、C言語のサブセット + Bash 拡張になっています。C言語にあるのと同じものについては、基本的に意味は同じです。ただ、C言語を使わない人もいると思うので、この記事単体で完結するように一つ一つ説明していくことにします。

A3.1 原子式

先ずは原子式 (それ以上小さく分解できない式) から説明します。C 言語と同様に 10進数リテラル、8進数リテラル、16進数リテラルと変数名を記述することができます。他に Bash 独自の機能として n 進数のリテラルも指定することができます。

A3.1.1: 10進数リテラル

[1-9] の数字で始まる数字の並びです。

10進数リテラル
echo $((1234)) # 結果: 1234

A3.1.2: 8進数リテラル

0 から始まる整数は 8 進表記と解釈されます。

8進数リテラル
echo $((0755)) # 結果: 493

A3.1.3: 16進数リテラル

0x もしくは 0X で始まる整数は 16 進数と解釈されます。

16進数リテラル
echo $((0xFF)) $((0X100)) # 結果: 255 256

A3.1.4 n進数リテラル

なんと好きな n 進数の表記を取り扱うこともできます。n#数 の形式で指定します。

n進数リテラル
echo $((2#10111011)) $((64#aK3)) # 結果: 187 43907

但し n は 2 以上 64 以下です。n が 36 以下の場合には a-z が 10-35 に対応し、大文字・小文字は区別されません。n が 36 より大きい場合には、a-z が 10-35 に対応し A-Z が 36-61 に対応します。そして @ が 62 で、_ が 63 になります。

n# に数字を一つも書かなかった場合 (例: $((10#))) は Bash 5.0 以下と 5.1 以上で振る舞いが異なります。Bash 5.0 以下では 0 と評価されますが、Bash 5.1 以上ではエラーになります。

A3.1.5 変数名

変数名も算術式に入れることができます。存在しない変数名や空の変数名を指定すると、評価結果は 0 になります。

変数名
i=1234 j=
echo $((i)) $((j)) $((k)) # 結果: 1234 0 0

A3.2 単項式

本当はより頻出の二項演算子から説明したいのですが、説明の都合上、先に単項演算子から説明します。

3.2.1 インクリメント・デクリメント

前置インクリメント ++変数名 は変数の中身を1増やします。前置デクリメント --変数名 は変数の中身を1減らします。評価結果は変更後の変数の中身です。

前置インクリメント・デクリメント
i=2016
echo $((++i)) # 結果: 2017
echo $i # 結果: 2017
echo $((--i)) # 結果: 2016
echo $i # 結果: 2016

後置インクリメント 変数名++ と後置デクリメント 変数名-- も同様ですが、評価結果が変更前の変数の中身ということが違います。

後置インクリメント・デクリメント
i=2016
echo $((i++)) # 結果: 2016
echo $i # 結果: 2017
echo $((i--)) # 結果: 2017
echo $i # 結果: 2016

A3.2.2 符号

式の前について符号を反転したり (-算術式)、あるいは符号が反転しないことを明示したり (+算術式) します。

符号
echo $((+1)) # 正号、結果: 1
echo $((-1)) # 負号、結果: -1

A3.2.2 ビット反転

ビット毎の反転を行います。Bash は内部的に intmax_t でビット反転を行いますが、負の数の内部表現は環境に依存する (大抵は2の補数表現ですが) ので、Bash 算術式のビット反転も環境に依存します。

ビット反転
echo $((~0)) # 結果: -1 (2の補数の場合)

A3.2.2 論理否定

!算術式 は論理否定を表します。算術式の評価結果が 0 以外の場合にそれを真と解釈し 0 を返します。算術式の評価結果が 0 の場合にそれを偽と解釈し 1 を返します。

論理否定
echo $((!1234)) # 結果: 0

A3.3 二項式

一番頻繁に使われると思われる演算子です。左辺と右辺のそれぞれに算術式を指定できます。

3.3.1 四則演算

特にこれが一番頻繁に使う算術演算ですね。

和・差・積・商・剰
echo $((5+2)) # 足し算、結果: 7
echo $((5-2)) # 引き算、結果: 3
echo $((5*2)) # かけ算、結果: 10
echo $((5/2)) # 割り算(端数切り捨て)、結果: 2
echo $((5%2)) # 剰り、結果: 1

A3.3.2 比較演算(関係演算子)

比較演算ももちろんあります。結果が真の時は 1 で、偽の時は 0 です。

等・不等
echo $((0==1)) # 等号、結果: 0
echo $((0!=1)) # 否定等号、結果: 1
echo $((0<1)) # 未満、結果: 1
echo $((0<=1)) # 以下、結果: 1
echo $((0>1)) # 大なり、結果: 0
echo $((0>=1)) # 以上、結果: 0

A3.3.3 ビットシフト

左右のビットシフトも使えます。Bash では内部的に intmax_t でシフトが行われますので、多くの環境で右シフトした時の余白には符号ビットが補填されます (符号拡張)。

シフト
echo $((1<<4)) # 左シフト、結果: 16
echo $((-1>>1)) # 右シフト、結果: -1 (符号拡張の場合)

A3.3.4 ビット毎の演算

ビット毎の演算も使えます。これも Bash は内部的に intmax_t のビット演算をそのまま実行するだけなので、負の数の場合にはその内部表現に依存して結果が環境によって変わり得ます。

ビット演算
echo $((1&2)) # ビット論理積、結果: 0
echo $((1|2)) # ビット論理和、結果: 3
echo $((1^3)) # ビット排他的論理和、結果: 2

A3.3.5 論理演算

論理積・論理和も計算できます。左辺および右辺は 0 以外の時に真と解釈され、0 の時に偽と解釈されます。結果は、真の時に 1 で偽のときに 0 になります。

論理積・論理和
echo $((1&&0)) # 結果: 0
echo $((1||0)) # 結果: 1

また、&& の右辺の評価結果が偽だったときには (右辺を評価しなくても全体の結果が偽と確定するので) 右辺は評価されません。同様に、|| の左辺が真だったときには右辺は評価されません。

論理積・論理和の短絡評価
i=0 j=0
echo $((i++ && j++)) # 結果: 0 
echo $i $j # 結果: 1 0 (上で i++ が偽になり、j++ は実行されない)

i=0 j=0
echo $((++i || ++j)) # 結果: 1
echo $i $j # 結果: 1 0 (上で ++i が真になり、++j は評価されない)

A3.3.6 代入演算子

変数に対する値の代入もできます。評価結果は代入後の変数の値です。

代入演算子
echo $((a=1234)) # 結果: 1234
echo $a # 結果: 1234

複合代入演算子も使えます。

複合代入演算子
a=1234
echo $((a+=10)) # a=a+10 に同じ、結果: 1244
echo $((a-=10)) # a=a-10 に同じ、結果: 1234
echo $((a*=10)) # a=a*10 に同じ、結果: 12340
echo $((a/=10)) # a=a/10 に同じ、結果: 1234
echo $((a%=10)) # a=a%10 に同じ、結果: 4
echo $((a<<=6)) # a=a<<6 に同じ、結果: 256
echo $((a>>=2)) # a=a>>2 に同じ、結果: 64
echo $((a|=63)) # a=a|63 に同じ、結果: 127
echo $((a&=63)) # a=a&10 に同じ、結果: 63
echo $((a^=15)) # a=a^10 に同じ、結果: 48

A3.3.7 カンマ演算子

カンマで区切って複数の式を指定することができます。評価結果は右辺の結果になります。

カンマ
echo $((a=1,b=2)) # 結果: 2
echo $a $b # 結果: 1 2

A3.3.8 累乗

Bash の拡張で累乗もできます。** という演算子です。

累乗
echo $((2**10)) # 結果: 1024

A3.4 配列要素

Bash には配列・連想配列が存在します。その要素を参照することもできます。

配列の場合には 配列名[算術式] とします。

配列要素
arr=(111 222 333)
echo $((arr[1])) # 結果: 222

連想配列の場合には 連想配列名[文字列] とします。

連想配列要素
declare -A dict=([hello]=1234 [world]=4321)
echo $((dict[world])) $((dict[hello])) # 結果: 4321 1234

配列・連想配列添字だけの特別ルールとして、添字を解釈する前にコマンド置換・変数展開・算術式展開などのシェル展開が 要素の参照時 に実行されます。ただし、$(()) に算術式が渡される前に展開されないように $ のエスケープ (つまり \$ のようにする) が必要です。

配列添字のシェル展開
arr=(111 222 333)
index=0
echo $((index=2,arr[\$(echo \$index)])) # 結果: 333

# 以下は、比較のため
index=0
echo $((index=2,arr[$(echo $index)])) # 結果: 111

連想配列の場合も同様です。

連想配列添字のシェル展開
declare -A dict=([hello]=1234 [world]=4321)
echo $((arr[\$(echo world)])) # 結果: 4321

A3.5 条件演算子

条件演算子 ?:式1?式2:式3 の形で使用します。先ず式1を評価します。式1の評価結果が真 (0以外) の場合に、式2を評価してその結果を返します。式1の評価結果が偽 (0) の場合に、式3を評価してその結果を返します。

条件演算子
echo $((1234?111:222)) # 結果: 111

式1の評価結果が真の場合は式3は評価されません。式1の評価結果が偽の場合は式2は評価されません (短絡評価)。

条件演算子の短絡評価
i=0 j=0
echo $((1234?i++:j++)) # i++ が実行される。j++は実行されない。
echo $i $j # 結果: 1 0

i=0 j=0
echo $((0?i++:j++)) # j++ が実行される。i++は実行されない。
echo $i $j # 結果: 0 1

A3.6 括弧と演算子の結合

3.6.1 括弧

複数の演算子を含む式においては、演算子の結合優先順位 (後述) に従って構文的な入れ子構造が決定されます。入れ子構造を明示的に指定して、結合の仕方を変更したい場合に括弧を使います。形式的には (算術式) の様な形になります。例を見るのが早いです。

echo $((1+2*3)) # 結果: 7 (先に 2*3 が計算されて 6 になり、次に 1+6 が計算される)
echo $(((1+2)*3)) # 結果: 9 (先に 1+2 が計算されて 3 になり、次に 3*3 が計算される)

1 + 2 * 3(1 + 2) * 3 ではなく 1 + (2 * 3) と解釈されます。先に加算を実行するためには必ず (1 + 2) * 3 と書かなければなりません。

A3.6.2 演算子の優先順位と結合性

よく考えたら括弧を使うためには、結合の規則を知っていないと片手落ちです。各演算子には結合の優先順位と結合性という性質が割り当てられています。

結合の優先順位: 算術式の中に複数の演算子が現れる場合、演算子の結合の優先順位が高いものから先に、演算子の左右にある式を演算の対象 (オペランド) として取り込みひとまとまりになります。例えば、1+2*3 という式を考えます。*+ よりも結合の優先順位が高いので、先に左右の式を取り込みます: 1+(2*3)。その後で + の左右にある式が加算の対象と見なされます: (1+(2*3))

結合性: 演算子には右結合か左結合かという性質もあります。これは、同じ優先順位の演算子が並んでいる場合に、どの演算子の周りにある式が先にひとまとまりになるかを表します。例えば +- は同じ優先順位ですが左結合の演算子なので、1+2-3+4-5 は左から順にくっついて (1+2)-3+4-5((1+2)-3)+4-5 → … → ((((1+2)-3)+4)-5) と解釈されます。

以下に各演算子の優先順位と結合性をまとめます。優先順位は番号の小さい方が高いことを表します。

優先順位 結合性 演算子式
1 左結合 a[添字]a++a--
2 右結合 ++a--a+a-a!a~a
3 右結合 a**b
4 左結合 a*ba/ba%b
5 左結合 a+ba-b
6 左結合 a<<ba>>b
7 左結合 a<ba<=ba>ba>=b
8 左結合 a==ba!=b
9 左結合 a&b
10 左結合 a^b
11 左結合 a|b
12 左結合 a&&b
13 左結合 a||b
14 右結合 a?b:c
15 右結合 a=b
a+=ba-=ba*=ba/=ba%=b
a<<=ba>>=ba&=ba^=ba|=b
16 左結合 a,b

上記の表で特筆すべきは Bash 拡張の累乗演算子 ** で、乗除演算子よりも優先順位が高く、前置演算子よりも優先順位が低く設定されています。さらに他の多くの二項演算子と違って右結合です (代入演算子と同じ)。つまり、2**2**3(2**2)**3 ではなく 2**(2**3) と解釈されます。

累乗演算子は右結合
echo $((2**2**3)) # 結果: 256 (先に 2**3 が計算されて 8 になる)
echo $(((2**2)**3)) # 結果: 64 (左から順に計算して欲しい時は括弧が必要)

A3.6.3 評価順序

優先順位の序でに Bash における評価順序について述べておきます。C言語的には式の評価順序 (特にカンマ以外の二項式) は多くの場合定められていません。しかし、Bash は実際の実装なので何らかの順序で評価が行われます。実のところ、Bash では二項式の左辺の評価が全て終わってから右辺の評価が実行されます。

A3.6.4 Bash の ++-- の字句解釈

ところで Bash では、X++++XX----X (但し、X には 変数名 または 配列名[式]連想配列名[文字列] の何れかが入ります) 以外の形で ++-- が現れると、それぞれ二つの +・二つの - として解釈されます。例えば、1++2 という並びは Bash では (1+(+2)) と解釈されて問題なく計算されます。

Bashでは ++ は文脈依存
echo $((1++2)) # 結果: 3

これは C 言語の振る舞いと全然違います。C言語では ++-- と言った並びは演算子の結合の仕方を調べる前に、一つの演算子であることが確定します。つまり、1++2 を考えると ++ という並びが現れた時点でそれが一つの演算子ということが確定するので、C言語ではどう結合させても 1++2 という列が全体として正しい式になることはありません。

そういえば、字句の規則といえば、今まで書き忘れていましたが原子式や演算子の前後に空白・改行類を挿入しても意味は変わりません。今更他に書く場所がないのでとってつけたようですが (というか実際にとってつけたのですが) ここに書くことにしました。

A4. Bash で算術式が使える場所

実は算術式を指定できる場所は算術式展開 $(( ~ )) の内側だけではありません。様々な場所で使うことができます。ここではそれらを列挙します。

A4.1 算術式展開

A4.1.2 算術式展開 $(( ~ ))

算術式展開は今まで説明した $(( 算術式 )) のことです。コマンドなどの評価に先立って算術式評価が行われ、その結果で置換されます。

算術式展開
echo $((1+2)) # echo 3 と解釈される

A4.1.2 非推奨 $[ ~ ]

更に算術式展開の隠し構文で $[ ~ ] というものもあります。但し、$[ ~ ] は Bash のドキュメントに記載されていない非推奨機能です。新しく Bash スクリプトを書くなら奇を衒わずに $(( ~ )) の形式の方を使うべきです。

※zsh では $[ ~ ] の形式もドキュメントに記載されています。

算術式展開 (非推奨)
echo $[1+2] # echo 3 と解釈される

A4.2 算術式の構文・コマンド

A4.2.1 ((~))

算術式を評価するコマンドになる構文 (( 算術式 )) もあります。終了ステータス $? は算術式の評価結果が 0 以外の時に成功 (0) で、0 の時・構文エラーの時に失敗 (1) です。

((算術式))
((a=1+2))
echo $? $a # 結果: 0 3

A4.2.2 let ~

算術式を評価する let というコマンドもあります。let 算術式 [算術式...] という形で指定します。各引数はそれぞれ独立した算術式として左から順に評価されます。

let コマンド
let a=1 b=2 c=a+b
echo $? $c # 結果: 0 3

引数は一つ以上指定する必要があります。let コマンドの終了ステータスは、最後の算術式の評価結果が、0 以外の時に成功 (0) で 0 の時・構文エラーの時に失敗 (1) です。

let は算術式専用の文法 (()) と違って通常のコマンドと同じ形式を取ります。つまり、()<>&*?[ などの文字はシェルコマンドの特別な文字と見なされてしまうので、算術式としてそのまま使えません。代わりに \ でエスケープするか、算術式全体を '~' などでクォートする必要があります。

let 記号類のクォート
let 2>1 # 駄目。2>1 はファイルディスクリプタ 2 から
        # ファイル 1 へのリダイレクトと見なされてしまう
let '2>1' # OK
let 2\>1 # OK

let (1+2)*3 # 駄目。シェルの構文エラー
let '(1+2)*3' # OK
let \(1+2\)\*3 # OK

let 2*3 # 駄目。例えば shopt -s nullglob のとき
        # * のパス名展開に失敗して式が消滅する。
        # もしくは一致したファイル名が算術式として実行される。
let '2*3' # OK
let 2\*3 # OK

この理由から基本的には let ではなく (()) を使う方が推奨されます。

A4.2.3 for ((~; ~; ~))

また for 文の特別な形式として for ((式1;式2;式3)) というものを使うことができます。C言語の for 文と同様に、式1 が一番初めに評価され、式2 の評価結果が真である間 for 文の中身を繰り返し実行します。ループの最後で次のループに移る前に 式3 が評価されます。

for文
for ((i=0;i<10;i++)); do
  echo $i
done

ループの継続条件 (式2) を省略した時は真と解釈され無限ループになりますので break が必要です。

Bash では for キーワードの後の空白は省略可能です (Zsh では省略できません)。

A4.3 配列の添字

A4.3.1 配列要素への代入 arr[~]=value

配列要素に代入を行う時の添字は算術式です: 配列名[算術式]=値

配列要素代入の添字
arr[1+1]=hello
arr[2*3]=world
declare -p arr # 結果: declare -a arr='([2]="hello" [6]="world")'

A4.3.2 配列初期化時の添字 arr=([~]=value)

配列を一括で初期化する時に指定する添字も算術式です: 配列名=([算術式]=値)

配列初期化時の添字
arr=([1+1]=hello [2*3]=world)
declare -p arr # 結果: declare -a arr='([2]="hello" [6]="world")'

A4.3.3 配列要素の展開時の添字 ${arr[~]}

配列要素を変数展開で参照する時 ${arr[算術式]} に指定する添字も算術式です。

配列要素の展開時の添字
arr=([2]=hello [6]=world)
echo ${arr[5+1]} # 結果: world

A4.3.4 配列要素 unset 時の添字 unset 'arr[~]'

配列要素を unset を用いて削除する時 unset 'arr[算術式]' に指定する添字も算術式です。

配列要素の unset 時の添字
arr=([0]=hello [1]=arithmetic [2]=world)
unset 'arr[(4-1)*2-5]'
echo "${arr[*]}" # 結果: hello world

※因みに unset に配列要素・連想配列要素を指定する時は必ず [] をクォートする必要があります (全体を囲んで "arr[~]"'arr[~]' の様にするか、arr\[~\] などの様にする)。クォートを忘れると、たまたま arr0 の様なファイルがカレントディレクトリにあったり、シェルのオプションで failglobnullglob が設定されたりしている時に誤動作します。

A4.4 パラメータ・変数展開の offset/length

パラメータ・変数展開では ${変数名:開始点} または ${変数名:開始点:長さ} とすることで変数の中身の部分文字列を取り出すことができます。Bash では、この時の開始点 (offset) 及び長さ (length) の部分に指定するのも算術式です。

パラメータ展開
a=hello
echo ${a:1+1} # 結果: llo
echo ${a:2-1:1+2} # 結果: ell

また、配列要素の列を展開するときの offset/length も同様に算術式です。

配列要素の展開
arr=(spring summer autumn winter)
echo "${arr[@]:2-1}" # 結果: summer autumn winter
echo "${arr[@]:0:1+1}" # 結果: spring summer

A4.5 整数属性のついた変数に代入する時 declare -i var=算術式

Bash のシェル変数には整数属性を付加することができます。整数属性の付いたシェル変数に代入 (変数=算術式) を行うと、代入しようとした文字列について算術式評価が実行され、その結果が変数に格納されます。

整数属性を付加するには declare/typeset/local/readonly コマンドに -i オプションを指定して変数を宣言します。

declare -i&32;変数名
declare -i var
var=1+2*3
echo $var # 結果: 7

もちろん宣言と同時に代入をした時も算術式評価が行われます (declare -i 変数名=算術式)。

readonly -i 変数名=算術式
readonly -i ABC=1*2*3
echo $ABC # 結果: 6

A4.6 条件コマンド内の整数比較演算子のオペランド [[ ~ -eq ~ ]]

また Bash の条件コマンド [[ ~ ]] の中で整数比較の演算子 -eq, -ne, -lt, -le, -gt, -ge を使う時、その左辺・右辺は算術式評価の対象です。つまり、[[ 算術式 -eq 算術式 ]] の様になります。

条件コマンドの整数の比較演算子
[[ 1+1 -eq 2-2 ]]; echo $? # 左辺 = 右辺、結果: 1
[[ 1+1 -ne 2-2 ]]; echo $? # 左辺 ≠ 右辺、結果: 0
[[ 1+1 -lt 2-2 ]]; echo $? # 左辺 < 右辺、結果: 1
[[ 1+1 -le 2-2 ]]; echo $? # 左辺 ≦ 右辺、結果: 1
[[ 1+1 -gt 2-2 ]]; echo $? # 左辺 > 右辺、結果: 0
[[ 1+1 -ge 2-2 ]]; echo $? # 左辺 ≧ 右辺、結果: 0

但し、test コマンドや [ コマンドの場合には、上記の演算子の引数は算術式評価の対象となりません。

この機能は長らくドキュメントに書かれていませんでしたが、Chet が 2017-03-04 に [bug-bash] Re: -eq and strings でドキュメントに書くと言いました。

A4.7 算術式で参照した変数・配列要素の中身

算術式中で参照した変数・配列要素の値を取り出す時にも、変数に格納された文字列に対して算術式評価が起こります。つまり、再帰的に算術式の呼び出しが起こります。

変数・配列要素の参照時に算術式評価
var=1+2*3
echo $var # 結果: 1+2*3
echo $((3*var)) # 結果: 21
                # 注意: 3*1+2*3 になってから評価されるのではなく、
                # var から値を取り出す時に算術式評価が起こって 3*7 が得られる。

但し、代入式 a=x の左辺に来た変数名・配列要素の場合には、中身の算術式評価は起こりません。

代入式の左辺にある変数名・配列要素は評価されない
i=0 expr=i++
echo $((expr=3,i)) # 結果: 0

多段階の再帰も可能です。

i=0
expr1=i++
expr2=expr1,expr1,expr1
echo $((expr2,expr2,i)) # 結果: 6

ただし、再帰の深さには限界があって、(現行の bash-3.0 ~ 4.4 で) 1022 段までしかできません。

A4.8 算術式の記述場所と評価前の置換

算術式を記述する文脈によって、算術式を評価する前に算術式にどのような処理が行われるかなどが異なります。

シェル展開: 記述がどの様に変換されて最終的な算術式になるかが、文脈に依って異なることに注意しなければなりません。

記号のクォート: また記述する場所によって、シェルの特別な意味を持つ文字と区別する為にクォートが必要な記号が存在します。

空の式: 算術式として空文字列を指定した場合の解釈は多くの場合 0 となりますが、文脈に依っては異なる意味になったり構文エラーを引き起こしたりします。

上記の要素に従って分類すると、算術式を記述する場所は以下のような 9 パターンがあることが分かります。Bash 4.4, 5.1, 5.2 に於いて段階的に $((~)), ((~)) のクォートの取扱いが変わっていることに注意する必要があります。

場所 空の式の解釈 クォートが
必要な文字
行われる展開(※1)
$((式))$[式] 0 A (Bash 4.3以前)
Aq (Bash 4.4以降)
((式)) 0 AQ (Bash 5.0以前)
Aq (Bash 5.1以降)
${var:式}
${var:式:式}
0 A
let 式 0 ()<>&*?[ AQTG
for ((式;式;式)) AQ (Bash 5.1以前)
Aq (Bash 5.2以降)
arr[式]=1
${arr[式]}
※2 A
arr=([式]=1) ※2 AQ
unset arr[式] ※2 ()<>&*?[ AQTG
declare -i index=算術式
index=算術式
[[ 算術式 -eq 算術式 ]]
0 ()<>& AQT
変数の中身 0 なし
  • ※1 ただし、"行われる展開"列の各文字は次の展開が行われることを表します:
    • A = 算術式展開・コマンド置換・変数展開
    • Q = クォート除去 ('~'"~"\? などの処理)
    • q = クォート除去 ("~" のみ)
    • T = チルダ展開・履歴展開・プロセス置換
    • G = パス名展開(グロブ)・ブレース展開
  • ※2 配列添字に指定する空の算術式は以下の結果になります:
    • 空文字列だと構文エラー
    • 空白文字を1つ以上含むと 0

また、クォートされていない ( の数と ) の数は一致していなければなりません。

括弧のバランス
for ((i=\(1+2\);i<10;i++)); do :; done # OK
for ((i=(1+2\);i<10;i++)); do :; done # 構文エラー
for ((i='(1'+2;i<10;i++))); do :; done # 構文エラー

A5. まとめ

まとめについては親記事の §1.2, §1.3, §1.4 をご参照下さい。

148
138
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
148
138