while readで配列(変数)が保存されない

前書き

while readを使って配列に変数を追加するbashスクリプトを書こうと思ったのですが、while readの中で変数をechoで表示すると出力されるのに、ループ外で変数を使用すると何も代入されていないことになっていて、ちょっと詰まったので解決策をメモしておく。

そもそもの原因

原因はwhile readにあるというより、パイプでファイルをwhile readに渡していたのが問題だった。

例えば次のサンプルコードは、

0
1
2
3
#!/bin/sh

declare -a array=() #配列の初期化
cat input.txt | while read number #パイプでinput.txtの中身をwhile readに渡す
do
    array+=(${number}) #配列に数字を追加していく
done

echo "${array[@]}" #代入した数字(0 ~ 3)が出力されるはず

空の配列を出力する。つまり、配列は初期化されたままであり、代入は行われていない。イメージはC言語などにおけるローカル変数のスコープに近いものだと勝手に思っている。

これは入力をパイプで渡したことによってwhile readブロックがサブシェルで実行されていることが原因である(らしい)[1, 2, 3]。

[1]| while read のwhileループ脱出にはbreakではなくexitを利用する(+それに関連するTips)
[2]シェルスクリプトのwhile文の中の変数を外で使う方法
[3]パイプ出力を現在のシェル上のwhileに喰わせる上手いやり方

解決策

[2]で言及されているようにヒアドキュメント[4]を使う。

#!/bin/sh

declare -a array=() #配列の初期化
while read number #パイプでinput.txtの中身をwhile readに渡す
do
    array+=(${number}) #配列に数字を追加していく
done <<EOF       #ここから
`cat input.txt`  #ヒアドキュメント
EOF              #ここまで

echo "${array[@]}" #代入した数字(0 ~ 3)が出力される

EOF(この文字は何でもいい)で囲まれた部分を標準入力としてwhile readに渡してくれるので、パイプで渡すのと同様の感覚で使うことができる。

[2]ではProcess Substitutionが解決策になる[3, 5]と書いてあるが、自分の環境では正しく動作しなかった(おそらく理解不足)。

[4]知ると便利なヒアドキュメント
[5]標準出力をファイルのように扱う方法、例えば2つのコマンドの出力結果のdiffを取るとか

後書き

pythonを使えばこんなこと気にせずに書けるような気もするが、いずれ引き継ぐことを考えてbashで書こうとしたのがこの問題のきっかけだった。ヒアドキュメントについて学べたので有意義だったが、参考記事が軒並み5年以上前(2022年現在)の記事なので、もしかしたら今はもっと良い方法があるのかもしれない。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です