既知の症状らしいですが、今頃知ったのでメモ。
Linuxの Shellスクリプトで何かしらのファイルを読みながら1行ずつループする処理をする場合、
最も単純に(わかりやすく)書くと以下のようになる。
cat data.txt | while read sIn; do
echo ' '${sIn}
done
echo ' '${sIn}
done
一見なんの問題もない。
しかし、このループの内部で、外側に渡すための変数を設定したい場合、
いきなり期待どおり動かなかったりする。
sCnj="data:"
cat data.txt | while read sIn; do
sCnj="${sCnj} ${sIn}"
done
echo ${sCnj} # 必ず "data:" のみしか表示されない!?
cat data.txt | while read sIn; do
sCnj="${sCnj} ${sIn}"
done
echo ${sCnj} # 必ず "data:" のみしか表示されない!?
Shellスクリプトは未定義の変数に対してもエラーを出さない処理系なので、この動作がバグ化するとすごく気付きにくい。
実は Shellスクリプトの | (パイプ)処理から先のコマンド(群)はパイプ元とは別プロセスで起動していて、
上記の記述方法では変数を操作するループ内部と外側(初期化の部分と表示の部分)では
全く別の変数を参照していることになってしまうらしいのだ。
# ただ、必ずしもすべての /bin/sh がそういう実装というわけでもないらしく、昔はこうではなかったらしい。
# パフォーマンスを上げるために、別プロセスを起動して直接やり取りをするように途中から shの実態の処理が変わったんじゃなかろうか。
回避策は | (パイプ)ではなく、リダイレクトからファイルを読むようにするのだそうだ。
sCnj="data:"
while read sIn; do
sCnj="${sCnj} ${sIn}"
done < data.txt
echo ${sCnj} # 正しく内容が表示される
while read sIn; do
sCnj="${sCnj} ${sIn}"
done < data.txt
echo ${sCnj} # 正しく内容が表示される
# ループが大きくなると、どこからファイルを読んでいるのかわかりづらくなりそうな・・・。
ただ、今度は逆に昔の /bin/sh ではこのリダイレクトが使えないものがあるらしい。
なんとまぁフクザツな。