私の変数が1つの「読み込み中」ループではローカルですが、一見似ている他のループではローカルではないのはなぜですか?

私の変数が1つの「読み込み中」ループではローカルですが、一見似ている他のループではローカルではないのはなぜですか?

$x以下のコードスニペットと異なる値を取得するのはなぜですか?

#!/bin/bash

x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?

答え1

正しい説明が提供されました。ジェスビリングスそしてギグドラゴンしかし、もう少し詳しく説明します。

ほとんどのシェル(Bashを含む)では、パイプの各端はサブシェルで実行されるため、シェルの内部状態(変数設定など)に対するすべての変更はパイプの対応するセグメントに制限されます。サブシェルから得られる唯一の情報は、出力内容(標準出力および他のファイル記述子の場合)と終了コード(0から255の間の数値)です。たとえば、次のコードスニペットはゼロを印刷します。

a=0; a=1 | a=2; echo $a

ksh(pdksh / mkshバリアントではなくAT&Tコードから派生したバリエーション)とzshでは、パイプラインの最後のエントリは親シェルで実行されます。 (POSIXでは両方の動作を許可します。)したがって、上記のコードスニペットは2を印刷します。オプションを設定することで、最新のbashでこの動作を得ることができますlastpipeshopt -s lastpipeインタラクティブシェルでは無効なタスク制御も使用する必要がありますset +m)。

便利なイディオムは、パイプ(またはパイプの右側のすべての項目)にwhileループの連続を含めることです。しかし、ここではwhileループは実際には非常に一般的です。

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}

答え2

可変範囲の問題があります。パイプの右側のwhileループで定義された変数には独自のローカルスコープコンテキストがあり、変数への変更はループの外には表示されません。 while ループは本質的に以下を得るサブシェルです。コピーシェル環境に対するすべての変更は、シェルが終了すると失われます。これを見てStackOverflowの質問

修正する:whileループと独自のサブシェルがパイプのエンドポイントであるため、重要な事実を見落とし、回答でこれを更新しました。

答え3

〜のように他の答えで言及、パイプラインのさまざまな部分がサブシェルで実行されるため、サブシェルで変更された内容はデフォルトのシェルに表示されません。

Bashだけを考えると、構造体に加えて2つの異なる解決策がありますcmd | { stuff; more stuff; }

  1. 入力リダイレクトプロセスの交換:

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"
    

    のコマンド出力は<(...)名前付きパイプのように見えます。

  2. このlastpipeオプションを使用すると、Bashはkshのように動作し、デフォルトのシェルプロセスでパイプラインの最後の部分を実行します。ジョブ制御が無効になっている場合にのみ機能します。つまり、対話型シェルでは機能しません。

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

    または

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '
    

もちろん、kshとzshもプロセスの交換をサポートしています。ただし、とにかくメインシェルでパイプラインの最後の部分を実行するので、これを回避策として使用する必要はありません。

関連情報