サブシェルで実行される while 文

コマンドの標準出力をパイプで受けた while 文は子プロセスとして起動されたサブシェル上で実行される。サブシェルで使われる変数はカレントシェル上の同名の変数を継承するが、それ自体を書き換えることはできない。実はシェルスクリプトを書き始めてしばらくの間、このことをよく理解していなかった。スコープの概念はないと聞いていたが、変数の値が期待通りにならないことがあり、かなりハマった(苦笑)。まずはカレントシェル上で実行される while 文。以下は直感的に予想される値が返ってくる。

x=0
while read line; do
        echo "$x $line"
        x=1
done < /proc/mounts
echo $x

問題となるのは次のコード。while 文は cat コマンドのために起動されたサブシェル上で実行される。よって while 文内の変数 x は初期値としてカレントシェルの変数 x の値を継承するが、代入そのものはサブシェル上の変数 x に対して行われるため、カレントシェル上の変数 x を書き換えることはできない。サブシェル上の while 文内から値を外に出したい場合は、その受け渡しに何らかの策を講じる必要がある。

x=0
cat /proc/mounts |
 while read line; do
        echo "$x $line"
        x=1
done
echo $x


サブシェル内からデータの受け渡し

サブシェル上の while 文における実行結果をカレントシェルに渡す方法を考えてみた。まず while 文は条件式が偽と判定されると終了してしまうため、そのままでは終了時の状態を返す処理が記述できない。回避策として read コマンドの終了ステータスは while に渡さず無限ループさせ、条件式は if 文で判定させることにした。if ブロック内に通常のループ処理、else ブロック内にループ終了後の処理を記述する。データの受け渡しは while の終了ステータスを経由している。

cat /proc/mounts |
 while :; do
    if read line; then
        echo "$line"
    else
        exit 1
    fi
done
echo "$?"

上記の方法だと扱えるのは数値に限られてしまう。さらに修正を加えたのが以下のコード。テキストファイルを逆順に表示するスクリプトである(これ自体に深い意味は無い)。ポイントとしては変数 buf にデータを蓄積させ、while の終了時にサブシェルの標準出力に送っている。これはそのままカレントシェルの変数 data に書き込まれる。おまけとしてサブシェル内での処理状況をカレントシェルにリダイレクトすることでコンソールに表示されるようにした(このデータはカレントシェルの変数 data には影響を与えない)。取り敢えずはこんなところで自己満足している。

CUR_SHELL=`tty`
data=`cat /proc/mounts |
 while :; do
    if read line; then
        buf="$line\n$buf"
        echo "Sub-shell: $line" > $CUR_SHELL
    else
        echo $buf
        break
    fi
done`
echo -e "$data"







タグ:

Linux Shellscript
最終更新:2008年09月14日 00:35