bash 3.2の変数と日本語

更新: 2026-10-17
bashmacOSlaunchdset -u文字コード

macOSに入っている /bin/bash は、バージョン3.2.57。ここで "$WT、" のように、変数の直後に日本語を書くと、変数が空になり、日本語の一部も消える。 Mac miniのホスト側スクリプトで、AIへの依頼文が途中で欠けたまま実行された。経緯は開発日記のminiに任せた初日、3回失敗したにある。ここでは、再現した挙動と、見つけ方、直し方だけを書く。

何が起きるか

次の1行で再現できる。

/bin/bash -c 'WT=abc; printf "%s" "[$WT、]" | od -An -tx1'

期待する出力は 5b 61 62 63 e3 80 81 5d([、abc、、、])。UTF-8のロケールでは、実際は次になる。

5b 80 81 5d

abc が消え、、(UTF-8で e3 80 81 の3バイト)の先頭1バイトも消えて、残り2バイトだけが出る。

そのまま書く: "$WT、"(UTF-8のロケール)変数名として読まれる範囲: WT と e3 (未設定なので空)$WTe38081残った2バイト。文字にならず、そのまま出る出力: [ 80 81 ] 期待: [ abc e3 80 81 ]波括弧で囲む: "${WT}、"変数名は閉じ括弧で決まる → abc${WT}e3 80 81出力: [ abc e3 80 81 ] そのまま出る
変数の直後の1バイト目が、変数名の一部として読まれる。波括弧で囲むと、変数名の終わりが決まる。

日本語だけではない

直後の文字を変えて、UTF-8のロケールと LC_ALL=C で出力のバイト列を比べた。変数は WT=abc、見ているのは出力のバイト列。

直後の文字文字のバイト列UTF-8のロケールLC_ALL=C
、e3 80 8180 81 だけ残る正常
をe3 82 9282 92 だけ残る正常
日e6 97 a597 a5 だけ残る正常
:(全角コロン)ef bc 9abc 9a だけ残る正常
→e2 86 9286 92 だけ残る正常
éc3 a9a9 だけ残る正常

表の文字は en_US.UTF-8 と C で測った。、 だけは ja_JP.UTF-8 と C.UTF-8 でも測り、en_US.UTF-8 と同じ結果だった。ASCII以外のすべての文字が対象で、日本語に限らない。 ロケールが C なら起きない。手元のシェルと、launchdやsshで起動したシェルでロケールが違うと、手元では通って夜間だけ壊れる、という形になりうる。

なぜロケールで変わるのかは、ソースを読んでいないので分からない。測った結果だけを書く。

壊れなかった書き方も測った。

  • "${WT}、"(波括弧で囲む)
  • "$WT"、(引用符を先に閉じる)
  • "$1、"(位置パラメータ)
  • "$WT,"(直後がASCIIの記号)

直後がASCIIの英数字("$WTx")なら、WTx という別の変数を読む。これは通常のbashと同じ挙動。

どこで止まるか

set -u(未設定の変数を使ったらエラーにする)を付けていても、書き方によって止まる場合と止まらない場合がある。すべて set -u を付け、UTF-8のロケールで測った。

書き方起きることスクリプトはecho "$WT、"x=$(echo "$WT、")cat <<EOF ... $WT、 ... EOFecho "$WT、"エラーで終了(終了コード127)エラーが出て、x は空になるエラーが出て、cat は0バイトエラーは出ない止まる外側は続く次の行へ進む気づけない直に書く(set -u あり)コマンド置換の中(set -u あり)ヒアドキュメントの中(set -u あり)set -u なし以降は実行されない外側のスクリプトは、空の値のまま続く終了コードは0。依頼文が欠ける80 81 だけが出力される
止まるのは、直に書いた場合だけだった。ほかは、エラーが出ても処理が先へ進む。

実際に起きたのは、3行目の形。AIへの依頼文を cat <<EOF で組み立てていて、その中に 合図のworktree名は $WT_NAME、コミットメッセージは… と書いていた。set -u を付けていたが、エラーは出ただけで、スクリプトは先へ進み、欠けた依頼文のままAIが実行された。 再現したときの出力は次のとおり。

line 5: WT�: unbound variable
次の行まで進んだ rc=1
出力ファイル: 0 バイト

WT のあとの文字化けが、変数名に飲み込まれた先頭1バイト。

見つける

書いた本人は、壊れる書き方に気づけない。見た目はふつうの日本語の文で、エラーになる経路は、失敗したときにしか通らない場所に隠れていることがある。 この落とし穴は、数日前(10月13日まで)にも別のスクリプトで踏み、そのときは落ちた1か所だけを直した。同じ書き方が残っていたので、ホスト側の全スクリプトを静的に調べるテストを入れた。

import re
pat = re.compile(r'\$[A-Za-z_][A-Za-z0-9_]*(?=[^\x00-\x7f])')

変数の直後がASCII以外の文字になる箇所を、全 .sh の、コメント行を除く各行から探す。見つかったら失敗にする。ASCII以外のすべてを対象にしているのは、上の表のとおり、日本語に限らないため。

同じ書き方は、最初に気づいた依頼文のほかに、2か所あった。どちらも、失敗したときにしか通らない経路にあった(待ち受けスクリプトの「作業場所が無い」のログと、型検査の「判断できない」のメッセージ)。通常の実行では一度も表に出ない。直したあと、ホスト側の全スクリプト(33ファイル)にこの検査を流し、今後は書くとテストが落ちる。

直す

  • 変数は、常に ${変数} と書く。
  • 日本語の直前に変数を置くときは、"$WT"、 のように引用符を先に閉じてもよい。
  • 依頼文のように、あとで中身を確かめられるものは、実行する前に中身を確かめる。 欠けると困る印(必須の文言・文字数)が入っているかを、AIを動かす前に調べる。

確かめていないこと

  • bash 4以降での挙動。このMacには3.2.57しか入っておらず、新しいbashでは測っていない。
  • Mac miniのホスト側のロケール。実際にどのロケールで壊れたかは、確かめていない。

関連

WIKI一覧へ