検討してください:
numbers="1 111 5 23 56 211 63"
max=0
for num in ${numbers[@]}; do
[ $num -gt $max ]\
&& echo "old -> new max = $max -> $num"\
&& max=$num
done | tee logfile
echo "Max= $max"
削除した場合| tee logfile
最大変数は211として正しく出力されますが、そのままにしておくとMax= 0
。
何が起こっている?
パイプの両側はサブシェルで実行されます。サブシェルは元のシェルプロセスのコピーであり、同じ状態で開始され、その後独立して進化します¹。サブシェルで設定された変数は、親シェルに戻ることはできません。
Bashでは、パイプを使用する代わりに プロセス置換 を使用できます。これは、ループが元のシェルで実行され、tee
のみがサブシェルで実行されることを除いて、パイプとほぼ同じように動作します。
for num in "${numbers[@]}"; do
if ((num > max)); then
echo "old -> new max = $max -> $num"
max=$num
fi
done > >(tee logfile)
echo "Max= $max"
その間、スクリプトのいくつかの点を変更しました。
if
を意味する場合は&&
を使用しないでください。コマンドの戻りステータスを使用すると、読みにくくなり、同じ動作をしません。パイプソリューションとサブシェルソリューションの間には小さな違いがあることに注意してください。パイプコマンドは両方のコマンドが終了すると終了しますが、プロセス置換のあるコマンドは、メインコマンドが終了するとプロセス置換のプロセスを待たずに終了します。つまり、スクリプトが終了したときに、ログファイルが完全に書き込まれていない可能性があります。
別のアプローチは、サブシェルから元のシェルプロセスへの通信に他のチャネルを使用することです。 一時ファイルを使用 (柔軟ですが、正しく実行することが困難です—一時ファイルを適切なディレクトリに書き込み、名前の競合を回避し(mktemp
を使用)、以下の場合でも削除します。エラー)。または、 別のパイプを使用 で結果を通知し、コマンド置換で結果を取得できます。
max=$({ { for … done;
echo "$max" >&4
} | tee logfile >&3; } 4>&1) 3&>1
tee
の出力はファイル記述子3に送られ、スクリプトの標準出力にリダイレクトされます。 echo "$max"
の出力は、コマンド置換にリダイレクトされるファイル記述子4に送られます。
Forループをパイプに入れると、変数をスーパーシェルに渡さないサブシェルで実行されます。
それはパイプを生き残ることはできませんが、これはうまくいきます:
numbers="1 111 5 23 56 211 63"
max=0
echo $max>maxfile
for num in ${numbers[@]}; do
[ $num -gt $max ]\
&& echo "old -> new max = $max -> $num"\
&& max=$num\
&& echo $max>maxfile
done | tee logfile
read max<maxfile
echo "Max= $max"