シェルスクリプト: パイプの先の while 問題

パイプの先のコマンドラインはサブシェルで実行される。 だからこういう処理は(zsh, ksh以外では)期待どおり行かない

grep hogehoge file | while IFS=: read user pawd rest; do
  u="$u${u+ }$user"
done
echo "hogehogeユーザは $u です!"

みたいにやろうとしても、シェル変数 u は、サブシェルで定義されたものなので done の次の行には渡らない。 while ループで何かかき集めた情報をループの直後で利用したいだけなのに。

じゃあどうする?

っていって、for に書き変えたり、grep の結果をいったんファイルに落としたり、 もとの構造を大きく変えちゃう改造案が多いんだけど、

フィルタコマンド | while read ...done

の流れって、考える順序といっしょで分かりやすい。 これを損なうことなく書き変えたい。コレ簡単。 次のようにグルーピングすればよい。

grep hogehoge file | {
  while IFS=: read user pswd rest; do
    u="$u${u+ }$user"
  done
  echo "hogehogeユーザは $u です!"
}

中括弧がCのブロック括りみたいでスコープの範囲が明確になって分かりやすい。

ちなみに今これを書くときの確認のため調べて初めて知ったのだが、 while read の読み取り先をリダイレクトに書き変えた、

while read x; do
 処理処理
done < file

は、Solarisの /bin/sh ではやっぱりサブシェル化されてしまってだめ。 /usr/xpg4/sh は上記のリダイレクトでもOKだし、パイプラインの末尾でもOK。

いずれにしても、「パイプ先の while でいじった変数をループを抜けてから使いたい」、 の解は「グルーピングする」だけでよろし。 グルーピングから抜けると変数が消えるのも好都合なケースが多い。 ただし ksh/zsh の場合はパイプの終端のみ現行プロセスになるので スコープをここだけに限定したい場合は { } の代わりに ( ) のほうがよいだろう。 いずれにせよこの方法なら Solaris sh でも他の sh でも bash でも ksh でも zsh でも変数を持ち越せる。