1. ホーム
  2. bash

なぜ "while read ..." 構造に入力されたときだけ "read" へのパイピングが機能するのか?重複

2023-08-14 20:33:23

質問

こんな感じで、プログラムの出力から環境変数への入力を読み取るようにしています。

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

そして、その結果は

-

AとBの両方は常に空です。私は、bashがパイプされたコマンドをサブシェルで実行し、基本的に1つがパイプされた入力を読むことを防ぐことについて読みました。しかし、次のようになります。

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

うまくいったようで、結果は

first-second

どなたか、このロジックを説明していただけませんか?つまり while ... done と同じシェルで実行されます。 echo と同じシェルで実行され、サブシェルでは実行されないのですか?

どのように解決するのですか?

に対するループを行う方法 標準入力 に対してループを行い、結果を変数に格納する方法

下の バッシュ (および他の シェル も同様)、何かを別のコマンドにパイプするときに | を使うと、暗黙のうちに フォーク を暗黙のうちに作成します。サブシェルは、現在のセッションの環境に影響を与えることはできません。

だから、これ。

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

計算される場所 合計 はメインスクリプトで再利用することができませんでした。

フォークの反転

を使うことで バッシュ プロセスの置換 , ここでドキュメント または ここでは文字列 のように、フォークを逆向きにすることができます。

ここでは文字列

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 の中で メインスクリプト .

にパイプを通す。 コマンドリスト

しかし、対外的にのみ動作するために 標準入力 に対してのみ動作させる場合は、スクリプトの一種を フォーク :

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 の中で使用することはできません。 メインスクリプト (最後の右中括弧の後 } ).

使用方法 ラストパイプ バッシュオプション

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 をスクリプトで実行すると、コンソールで実行した場合とスクリプトで実行した場合で異なる動作になります。これでは、理解するのもデバッグするのも簡単にはいかないでしょう...。