Bash5.1の仕様変更に巻き込まれてCIが落ちた話
はじめに
この投稿はZennへの投稿テストを兼ねています。
https://qiita.com/gecko655/items/5cae0c8f48854c6bca64 と全く同じ内容です。
発生したこと
bashでは以下のようにarrayを定義することができます。
$ arr=("a" "b")
$ echo ${arr[0]}
a
$ echo ${arr[1]}
b
$ echo ${arr[@]}
a b
arrayに空文字が含まれていると、想定したとおりに動きます。
$ arr=("" "a")
$ echo ${arr[0]}
$ echo ${arr[1]}
a
$ echo ${arr[@]}
a
しかし、ここでarray変数に対してhere stringリダイレクトをしたときに、bash5.0以前と5.1以降で異なる挙動をするようです。
例えば↓を実行してみます。
arr=("" "a")
tee -a <<< ${arr[@]} | hexdump -C # どのようなバイト列が得られたのかを表示する
(ここから先の検証ではdockerを使います。便利な世の中ですね)
# Bash 5.0
$ docker run --rm bash:5.0 bash -c 'arr=("" "a"); tee -a <<< ${arr[@]} | hexdump -C'
00000000 61 0a |a.|
00000002
# Bash 5.1
$ docker run --rm bash:5.1 bash -c 'arr=("" "a"); tee -a <<< ${arr[@]} | hexdump -C'
00000000 20 61 0a | a.|
00000003
Bash 5.1 では、 スペースを表す 20
が先頭に出力されるようになりました。
空文字がarrayに含まれているときのhere stringリダイレクト周りの挙動が、Bash 5.0〜5.1の間で変更されたようです。
- Bash 5.1のリリースノートを読んだところ、↓この部分がこの仕様変更と対応しているっぽい
c. Here documents and here strings now use pipes for the expanded document if
it's smaller than the pipe buffer size, reverting to temporary files if it's
larger.
-
bashのコミットログは5.0〜5.1の差分を1コミットにsquashされているため、どの差分がこの仕様変更に関わっているのかよくわからない
-
array変数をhere stringでリダイレクトするのではなくarray変数をファイル出力すると、Bash 5.1のhere stringリダイレクトの挙動と同様に先頭にスペース(
20
)が入る。
$ docker run --rm bash:5.0 bash -c 'arr=("" "a"); echo "${arr[@]}" > tmpfile; cat tmpfile | hexdump -C '
00000000 20 61 0a | a.|
00000003
$ docker run --rm bash:5.1 bash -c 'arr=("" "a"); echo "${arr[@]}" > tmpfile; cat tmpfile | hexdump -C '
00000000 20 61 0a | a.|
00000003
つまり……
x | bash 5.0 | bash 5.1 |
---|---|---|
空白つきarrayをhere stringする | スペースが入らない | スペースが入る |
空白つきarrayをファイル出力する | スペースが入る | スペースが入る |
Bash 5.0以前でhere stringリダイレクト時にarrayの空文字要素が出力されなかったのは不具合っぽい?
Bashのバージョン差(しかもマイナーバージョン)の影響を受けることなんてあるんだな〜と思いました(小並感)。
Q&A
なんでBashのバージョンをアップデートしたの?
能動的にBashのバージョンをアップデートしようとしたわけではなく、CI(GCP cloud build)で使っているBashのバージョンが勝手にアップデートされてしまい、今回の問題が発生しました。
今回のCIでは、↓にある「CloudBuildに初めから存在するDockerImage」を使うことで、ビルド用Docker Imageをpullしないで済むようにしています。
- 具体的には
gcr.io/google.com/cloudsdktool/cloud-sdk:cloudbuild_cache
を使っています。
このイメージはなんらかのDebianっぽいLinuxになんらかのBash及びcloudsdkがインストールされているイメージで、cloudbuild_cache
タグはcloud buildのジョブ内以外では見えないタグです。そのため、このタグは任意のタイミングで更新される可能性があります。また、最新のバージョンが使われている保証もありません。
- cloudbuild_cache タグのイメージについては特にGCPのドキュメントに書いてあるような仕様ではないのですが、Googleの方に直接聞いたところ、「最新のイメージが使われること等は保証できないが、使っても大丈夫」との返答をいただいています。
このイメージが2022-10-29〜2022-10-30の間に更新され、Bashのバージョンが5.1に上がったため、 なにもしてないのにCIが落ちるようになった のでした。
この仕様変更でCIが落ちるってどんなCI実装だったの?
空arrayを初期化する際に、
arr=()
とすべきところが、なぜか
arr=("")
になっていました。
arr=("")
では長さ0の配列ではなく、空文字の要素が入っている長さ1の配列が定義され、Bash 5.0以前ではこれらの2つに挙動の差がなく気が付かなかったのですが、Bash 5.1にアップデートされたことで問題が顕在化しました。
Discussion