2008.03.25

Shellスクリプトのループで設定した変数が“外側”で参照できない?

既知の症状らしいですが、今頃知ったのでメモ。


Linuxの Shellスクリプトで何かしらのファイルを読みながら1行ずつループする処理をする場合、
最も単純に(わかりやすく)書くと以下のようになる。

cat data.txt | while read sIn; do
 echo ' '${sIn}
done

一見なんの問題もない。


しかし、このループの内部で、外側に渡すための変数を設定したい場合、
いきなり期待どおり動かなかったりする。

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} # 正しく内容が表示される

# ループが大きくなると、どこからファイルを読んでいるのかわかりづらくなりそうな・・・。

ただ、今度は逆に昔の /bin/sh ではこのリダイレクトが使えないものがあるらしい。
なんとまぁフクザツな。

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

photo
ichikawa