78

私は次のようにプログラム出力から環境変数への入力を読み込もうとしています。

echo first second | read A B ; echo $A-$B 

そして結果は次のとおりです。

-

AとBは常に空です。サブシェルでパイプされたコマンドを実行するbashと、基本的に入力をパイプして読み取れないようにすることについて読みました。ただし、次のとおりです。

echo first second | while read A B ; do echo $A-$B ; done

動作しているようですが、結果は次のとおりです。

first-second

誰かがここでの論理は何であるか説明できますか?while...コンストラクト内のコマンドは、done実際にはサブシェルではなく同じシェルで実行されechoますか?

4

4 に答える 4

74

stdinに対してループを実行し、結果を変数に格納する方法

(および他のシェル)では、を介して何かを別のコマンドにパイプすると、現在のセッションの子であるサブシェルであるフォーク|が暗黙的に作成されます。サブシェルは、現在のセッションの環境に影響を与えることはできません。

したがって、この:

TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
    done
echo final total: $TOTAL

期待した結果は得られません!:

  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343
echo final total: $TOTAL
final total: 0

計算されたTOTALをメインスクリプトで再利用できなかった場合。

フォークを反転させる

プロセス置換ヒアドキュメント、またはヒア文字列を使用すると、フォークを逆にすることができます。

ここに文字列

read A B <<<"first second"
echo $A
first

echo $B
second

ヒアドキュメント

while read A B;do
    echo $A-$B
    C=$A-$B
  done << eodoc
first second
third fourth
eodoc
first-second
third-fourth

ループの外側:

echo : $C
: third-fourth

ここでコマンド

TOTAL=0
while read A B;do
    ((TOTAL+=A-B))
    printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
  done < <(
    printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664
)
  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343

# and finally out of loop:
echo $TOTAL
-343

$TOTALこれで、メインスクリプトで使用できます。

コマンドリストへのパイピング

ただし、 stdinに対してのみ機能する場合は、フォークに一種のスクリプトを作成できます。

printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 | {
    TOTAL=0
    while read A B;do
        ((TOTAL+=A-B))
        printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
    done
    echo "Out of the loop total:" $TOTAL
  }

あげる:

  9 -   4 =    5 -> TOTAL=    5
  3 -   1 =    2 -> TOTAL=    7
 77 -   2 =   75 -> TOTAL=   82
 25 -  12 =   13 -> TOTAL=   95
226 - 664 = -438 -> TOTAL= -343
Out of the loop total: -343

注:メインスクリプト$TOTALでは使用できませんでした(最後の右中括弧の後)。}

ラストパイプbashオプションの使用

@CharlesDuffyが正しく指摘しているように、この動作を変更するために使用されるbashオプションがあります。ただし、このためには、最初にジョブ制御を無効 にする必要があります。

shopt -s lastpipe           # Set *lastpipe* option
set +m                      # Disabling job control
TOTAL=0
printf "%s %s\n" 9 4 3 1 77 2 25 12 226 664 |
  while read A B;do
      ((TOTAL+=A-B))
      printf "%3d - %3d = %4d -> TOTAL= %4d\n" $A $B $[A-B] $TOTAL
    done

  9 -   4 =    5 -> TOTAL= -338
  3 -   1 =    2 -> TOTAL= -336
 77 -   2 =   75 -> TOTAL= -261
 25 -  12 =   13 -> TOTAL= -248
226 - 664 = -438 -> TOTAL= -686

echo final total: $TOTAL
-343

これは機能しますが、これは標準ではなく、スクリプトを読みやすくするのに役立たないため、私は(個人的に)これが好きではありません。また、ジョブ制御を無効にすると、この動作にアクセスするのに費用がかかるようです。

注: ジョブ制御は、対話型セッションでのみデフォルトで有効になっています。したがってset +m、通常のスクリプトでは必要ありません。

したがってset +m、スクリプトで忘れると、コンソールで実行した場合とスクリプトで実行した場合に異なる動作が発生します。これでは、これを理解したりデバッグしたりするのは簡単にはなりません...

于 2012-12-07T13:25:30.787 に答える
28

はるかにクリーンな回避策...

first="firstvalue"
second="secondvalue"
read -r a b < <(echo "$first $second")
echo "$a $b"

このように、読み取りはサブシェルで実行されません(サブシェルが終了するとすぐに変数がクリアされます)。代わりに、使用する変数は、親シェルから変数を自動的に継承するサブシェルにエコーされます。

于 2016-06-17T16:49:58.940 に答える
23

最初に、このパイプチェーンが実行されます。

echo first second | read A B

それから

echo $A-$B

はサブシェルで実行されるためread A B、AとBは失われます。これを行う場合:

echo first second | (read A B ; echo $A-$B)

次に、両方read A Becho $A-$Bが同じサブシェルで実行されます(bashのマンページを参照して、(list)

于 2012-12-07T13:35:42.660 に答える
3

表示されているのは、プロセス間の分離です。readサブシェルで発生します。メインプロセス(echo後でコマンドが発生する場所)の変数を変更できない別個のプロセスです。

パイプライン(のような)は、通常はシェルのコンテキストで(同じプロセスで)実行さA | Bれるビルトイン(のような)の場合でも、各コンポーネントをサブシェル(個別のプロセス)に暗黙的に配置します。read

「配管中」の場合の違いは幻想です。同じルールが適用されます。ループはパイプラインの後半であるため、サブシェル内にありますが、ループ全体が同じサブシェル内にあるため、プロセスの分離は適用されません。

于 2014-09-25T23:20:13.890 に答える