私はこのようなプログラム出力から環境変数への入力を読み取ろうとしました:
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
と同じシェルで実際に実行されるのですか?
bash (および他の Shell も)の下で、|
を使用して別のコマンドに何かをパイプすると、暗黙的にforkが作成されます。 =、現在のセッションの子であり、現在のセッションの環境に影響を与えないサブシェル。
したがって、この:
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 [〜#〜]はメインスクリプトで再利用できませんでした。
bashProcess Substitution、Here DocumentsまたはHere Stringsを使用すると、フォークを逆にすることができます。
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に対してのみ作業する場合、forkに一種のスクリプトを作成できます。
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オプションがあります。ただし、このためには、最初にdisablejob controlにする必要があります。
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
を忘れると、コンソールで実行する場合またはスクリプトで実行する場合に異なる動作が発生します。これにより、これを理解したりデバッグしたりするのが簡単になりません...
まず、このパイプチェーンが実行されます:
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 B
およびecho $A-$B
は同じサブシェルで実行されます(bashのマンページを参照、(list)
よりクリーンな回避策...
read -r a b < <(echo "$first $second")
echo "$a $b"
この方法では、読み取りはサブシェルで実行されません(サブシェルが終了するとすぐに変数がクリアされます)。代わりに、使用する変数は、親シェルから変数を自動的に継承するサブシェルにエコーされます。
あなたが見ているのはプロセス間の分離です:read
はサブシェルで発生します-echo
コマンドが後で発生するメインプロセスの変数を変更できない別のプロセスです。
パイプライン(A | B
)通常、シェルのコンテキストで(同じプロセスで)実行されるビルトイン(read
など)の場合でも、暗黙的に各コンポーネントをサブシェル(個別のプロセス)に配置します。
「Piping into while」の場合の違いは幻想です。同じルールが適用されます:ループはパイプラインの後半であるため、サブシェルにありますが、wholeループはsameサブシェルにあるため、分離プロセスの適用されません。