web-dev-qa-db-ja.com

変数が1つの「while read」ループでローカルであるのに、一見似ている別のループではローカルではないのはなぜですか?

以下のスニペットから$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?
24
Peter.O

正しい説明はすでに jsbillings および geekosaur で示されていますが、少し詳しく説明します。

Bashを含むほとんどのシェルでは、パイプラインの両側がサブシェルで実行されるため、シェルの内部状態の変更(変数の設定など)は、パイプラインのそのセグメントに限定されたままになります。サブシェルから取得できる唯一の情報は、サブシェルが出力するもの(標準出力およびその他のファイル記述子)とその終了コード(0〜255の数値)です。たとえば、次のスニペットは0を出力します。

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

Ksh(pdksh/mkshバリアントではなく、AT&Tコードから派生したバリアント)およびzshでは、パイプラインの最後のアイテムが親シェルで実行されます。 (POSIXは両方の動作を許可します。)したがって、上記のスニペットは2を出力します。

便利なイディオムは、whileループ(またはパイプラインの右側にあるものは何でも、ここではwhileループが実際に一般的です)の継続をパイプラインに含めることです。

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

変数スコープの問題が発生しています。パイプの右側にあるwhileループで定義された変数には独自のローカルスコープコンテキストがあり、変数への変更はループの外では表示されません。 whileループは基本的に、シェル環境の[〜#〜] copy [〜#〜]を取得するサブシェルであり、環境への変更があります。シェルの最後で失われます。これを見てください StackOverflowの質問

[〜#〜]更新[〜#〜]:それ自体のサブシェルを持つwhileループが原因であるという重要な事実を指摘するのを怠っていましたパイプのエンドポイントなので、答えを更新しました。

9
jsbillings

他の回答で説明 のように、パイプラインの一部はサブシェルで実行されるため、そこで行われた変更はメインシェルには表示されません。

Bashのみを検討する場合、_cmd | { stuff; more stuff; }_構造体に加えて、他に2つの回避策があります。

  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でもサポートされています。ただし、いずれにしてもメインシェルでパイプラインの最後の部分を実行するため、回避策として使用する必要はありません。

7
ilkkachu
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

それは働くことができます。

0
GPS