パイプを使って、whileに渡し変数を操作すると、whileを抜けた後で変数を参照しても値が格納されない場合の対処方法です。
原因は、whileは別プロセスとして動作するためwhile内であれば、値は保存されていますが、whileを抜けるとその値は破棄されてしまうためです。
#!/bin/bash # hostsの行をカウントします i=0 cat /etc/hosts | while read L do i=`expr $i + 1` echo inside: $i done echo outside: $i
実行結果
$ ./while1.sh inside: 1 inside: 2 inside: 3 inside: 4 inside: 5 inside: 6 inside: 7 inside: 8 <snip> outside 0
whileを抜けた後に、$iを出力すると、0になっています。
パイプを使わず、リダイレクトを使う。
#!/bin/bash # hostsの行をカウントします i=0 while read L do i=`expr $i + 1` echo inside: $i done < /etc/hosts echo outside: $i
実行結果
$ ./while2.sh inside: 1 inside: 2 inside: 3 inside: 4 inside: 5 inside: 6 inside: 7 inside: 8 <snip> outside: 43
意図した動作になっています。
しかし、リダイレクトでファイルを指定していますが、stdin(pipe)からの入力の場合はどうするのだ?という問題があります。
解決方法は、以下に記します。
Process Substitutionを使うことにより解決します。
man bashをするとProcess Substitutionについて記述されています。
$ LANG=C man bash
<snip>
Process Substitution
Process substitution is supported on systems that support named pipes (FIFOs) or the /dev/fd method
of naming open files. It takes the form of <(list) or >(list). The process list is run with its
input or output connected to a FIFO or some file in /dev/fd. The name of this file is passed as an
argument to the current command as the result of the expansion. If the >(list) form is used, writ-
ing to the file will provide input for list. If the <(list) form is used, the file passed as an
argument should be read to obtain the output of list.
When available, process substitution is performed simultaneously with parameter and variable expan-
sion, command substitution, and arithmetic expansion.
<snip>
$ man bash
<snip>
プロセス置換
プロセス置換 (process substitution) がサポートされるのは、 名前付きパイプ (FIFO) または ファイル・ディスクリプターの /dev/fd 形式で
の指定 をサポートしているシステムです。これは <(list) または >(list) の形になります。 プロセス list は、その入力や出力が FIFO または
/dev/fd 中の 何らかのファイルに接続された状態で実行されます。 このファイルの名前は、展開の結果として、 引き数の形で現在のコマンドに
渡されます。 >(list) の形式を使った場合、 ファイルへの書き込みは list への入力となります。 <(list) の形式を使った場合、 引き数として
渡されたファイルは list の出力を得るために読み込まれます。
利用可能であれば、プロセス置換 (process substitution) は、 パラメータ展開、変数展開、コマンド置換、算術式展開と同時に行われます。
<snip>
実際にstdinから記述したサンプルスクリプトを以下に記します。
関数にしてみました。
#!/bin/bash
CountLineStdin() {
if [ ! -p /dev/stdin ]; then
echo "stdin empty!"
return 1
fi
i=0
while read line
do
i=`expr $i + 1`
echo "inside: $i"
done < <(cat -)
return 0
}
CountLineStdin
echo "outside: $i"
実行結果 $ cat /etc/hosts | ./while3.sh
inside: 1 inside: 2 inside: 3 inside: 4 inside: 5 inside: 6 inside: 7 inside: 8 <snip> outside: 43
上記出力の通り、期待した動作になっています。
記述に関しての詳細は、man bashおよび、ネット調べてください。
重要なのは、while do ... done の <の記述になります。
done < <(cat -)と記述してください。
スクリプトを簡単に説明すると ! -p /dev/stdin でパイプによる入力があるかを確認しています。
cat -はstdinを出力しています。
以上、while内部の変数が反映されない場合の対処方法でした。