結論
Bash に空の配列は無い。空の配列を値に持つと変数は unset される。unset された変数を空の配列として扱いたいなら set -u
をしないか、set -u
するなら "${array[@]+"${array[@]}"}"
という表現を使う。
(この記事で使っている Bash は 4.3.48 である。Ubuntu 16.04 で実行。)
空の配列
Bash には配列が用意されていて、以下のように使う。
words=(foo bar baz)
for w in "${words[@]}"; do
echo "( $w )"
done
これを実行すると以下のようになる。
( foo )
( bar )
( baz )
これは当然、配列が空の場合も動作すると期待するだろうし、実際動作しているように見える。
words=()
for w in "${words[@]}"; do
echo "( $w )"
done
↓
しかしシェルスクリプトを書くときに常用する set -u
(代入していない変数を使うとエラーで停止)を使うと...
set -u
words=()
for w in "${words[@]}"; do
echo "( $w )"
done
↓
bash: line 3: words[@]: unbound variable
declare
を使って明示的に配列と宣言しても駄目。
set -u
declare -a words=()
for w in "${words[@]}"; do
echo "( $w )"
done
↓
bash: line 3: words[@]: unbound variable
仕様をチェック
そこで、man bash (1) をチェックしてみよう。空の配列のはっきりした説明は出てこないが、以下のような記述がある。
An array variable is considered set if a subscript has been assigned a value. The null string is a valid value.
要素を持たない配列変数は unset のようだ(はっきりと書いているわけではないが)。
実際、明示的に要素を削除して空配列にすると unset になるのが観測できる。
set -u
words=(foo)
unset words[0]
echo $words
↓
bash: line 4: words: unbound variable
対処法
配列が空のとき ${words[@]}
という表現が、words
が unset なのでエラーになる。よって set のときのみ ${words[@]}
が評価されるようにすればいい。こういうとき Bash では ${var+var}
という表現を使う。
set -u
words=()
for w in "${words[@]+"${words[@]}"}"; do
echo "( $w )"
done
↓
配列の要素数を数える表現はそのまま使える。
set -u
words=()
echo ${#words[@]}
↓
0
後記
そのまんまの質問が Stackoverflow にあった : Bash empty array expansion with set -u
- Stack Overflow
この記事によると Bash 4.4 では ${words[@]}
が unset のときにエラーにならずに空配列として扱えるようだ。ただし man の記述はそのままらしいので、Bash のバグか man のバグかは不明の由。