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年現在)の記事なので、もしかしたら今はもっと良い方法があるのかもしれない。