web-dev-qa-db-ja.com

Bash:T型を使用したforループ内の変数のスコープ

検討してください:

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

何が起こっている?

6
tetris11

パイプの両側はサブシェルで実行されます。サブシェルは元のシェルプロセスのコピーであり、同じ状態で開始され、その後独立して進化します¹。サブシェルで設定された変数は、親シェルに戻ることはできません。

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"

その間、スクリプトのいくつかの点を変更しました。

パイプソリューションとサブシェルソリューションの間には小さな違いがあることに注意してください。パイプコマンドは両方のコマンドが終了すると終了しますが、プロセス置換のあるコマンドは、メインコマンドが終了するとプロセス置換のプロセスを待たずに終了します。つまり、スクリプトが終了したときに、ログファイルが完全に書き込まれていない可能性があります。

別のアプローチは、サブシェルから元のシェルプロセスへの通信に他のチャネルを使用することです。 一時ファイルを使用 (柔軟ですが、正しく実行することが困難です—一時ファイルを適切なディレクトリに書き込み、名前の競合を回避し(mktempを使用)、以下の場合でも削除します。エラー)。または、 別のパイプを使用 で結果を通知し、コマンド置換で結果を取得できます。

max=$({ { for … done;
          echo "$max" >&4
        } | tee logfile >&3; } 4>&1) 3&>1

teeの出力はファイル記述子3に送られ、スクリプトの標準出力にリダイレクトされます。 echo "$max"の出力は、コマンド置換にリダイレクトされるファイル記述子4に送られます。

Forループをパイプに入れると、変数をスーパーシェルに渡さないサブシェルで実行されます。

5
Tagwint

それはパイプを生き残ることはできませんが、これはうまくいきます:

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"  
2
MikeD