Bashで全角文字の直後に「unbound variable」が出る謎エラーの正体
日本語ログを大量に出す Bash スクリプトで、set -u を付けた途端に VAR<化けた文字>: unbound variable という意味不明なエラーが出たことはないだろうか。変数はちゃんと定義しているのに、だ。結論から言うと、変数参照の直後に全角文字を置いたせいで、マルチバイト文字の先頭バイトが変数名の一部として誤解釈されている。直し方は ${VAR} と波括弧で囲むだけ。地味だが知らないと延々ハマる。
再現する書き方
set -u
SIGN_IDENTITY="Developer ID"
echo "署名: $SIGN_IDENTITY(有効)" # ← ここで落ちる
このとき Bash はこんなエラーを吐く。
line 3: SIGN_IDENTITY<0xEF>: unbound variable
SIGN_IDENTITY は定義済みなのに「unbound」と言われる。意味が分からず時間を溶かしやすい。
原因: 変数名の終端判定とマルチバイト
Bash は $VAR の変数名を「英数字・アンダースコア以外の文字が来たら終端」と判定する。ところが全角の ((全角括弧)はUTF-8で複数バイトに分かれており、**その先頭バイトが「英数字でもアンダースコアでもない、けれど名前の続きとして読まれてしまう」**境界ケースを踏む。
結果として SIGN_IDENTITY の直後の全角バイトまで変数名に巻き込まれ、「そんな変数は無い(unbound)」となる。set -u(未定義変数をエラーにする)を有効にしていると、これが実行時エラーとして顕在化する。
解決策: ${VAR} で範囲を明示する
変数名の範囲を波括弧で囲んで明示すれば、直後に何が来ても誤解釈されない。
# ❌ NG
echo "署名: $SIGN_IDENTITY(有効)"
# ✅ OK
echo "署名: ${SIGN_IDENTITY}(有効)"
日本語コメント・ログを多用するスクリプトでは、変数参照を常に ${} で囲む癖をつけておくのが一番の予防策になる。
一括で検出する
既存スクリプトに潜む地雷は、grep で「変数参照の直後に非ASCII文字」を探せば洗い出せる。
grep -nP '\$[A-Za-z_][A-Za-z0-9_]*[^\x00-\x7F]' script.sh
ヒットした箇所を ${} で囲んでいけばよい。
⚠️ この手のエラーは
bash -n script.sh(構文チェック)では検出できない。 構文上は正しく、実行して初めて落ちる点に注意。
さらにシェルを極めるなら
シェルスクリプトは「動けばいい」で書くと、こういう境界ケースで足元をすくわれる。堅牢なスクリプトの書き方を体系的に学んでおくと、運用フェーズでの事故がぐっと減る。
まとめ
$VARの直後に全角文字を置くと、先頭バイトが変数名に巻き込まれてunbound variableになる- 解決策は
${VAR}と波括弧で範囲を明示するだけ grep -nP '\$[A-Za-z_][A-Za-z0-9_]*[^\x00-\x7F]'で一括検出できるbash -nでは検出できない実行時エラーなので、日頃から${}で囲む習慣を