以下のコードはLinuxシステムで実行されている単純なbashスクリプトで、各出力間の時間間隔が次の理由を知りたいと思います。4つ 第二変える八?
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:27 AM +07
Sun 11 Apr 2021 12:42:31 AM +07
Sun 11 Apr 2021 12:42:35 AM +07
後者の時間値を少し増やしても、各出力間の時間はまだ4秒です。
$ for test in test1 test2 test3; do (echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 12:42:44 AM +07
Sun 11 Apr 2021 12:42:48 AM +07
Sun 11 Apr 2021 12:42:52 AM +07
これはとても混乱していますが、誰もがこれを説明できればとても感謝します。
より混乱するのは、コマンドを前に置くとdate
スリープコマンドが実行されないように見えることです。
$ for test in test1 test2 test3; do (date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date; done
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
Sun 11 Apr 2021 01:22:35 AM +07
答え1
これを明確にするために「hop2」をエコーする前と後(パイプバイパス)stderrにデバッグ出力を追加します。
$ for test in test1 test2 test3; do
(echo ${test}; sleep 4s; echo before hop2 >&2;
echo hop2; echo after hop2 >&2; sleep 4s; echo hop3) | date;
done
Sat Apr 10 11:29:46 PDT 2021
before hop2
Sat Apr 10 11:29:50 PDT 2021
before hop2
Sat Apr 10 11:29:54 PDT 2021
before hop2
気づくecho after hop2 >&2
絶対に実行しないでください、そしてそれに続くコマンド:2番目sleep
とecho hop3
。
私が理解したのは、これが起こったことです。ループ内では、2つの別々のプロセスが並列に実行され、最初のプロセスの出力が2番目のプロセスの入力に接続されます。 2つのプロセスが実行されます。
echo ${test}
sleep 4s
echo before hop2 >&2
echo hop2
echo after hop2 >&2
sleep 4s
echo hop3
そして
date
おおよその実行順序は次のとおりです(最初のステップ3の正確な順序とステップ4の開始はややランダムです)。
- プロセス1が実行されます
echo ${test}
。これは、「test1」(および改行文字)をパイプに書き込んで後で読み取れるようにバッファリングされます。 - プロセス2を実行し
date
、現在の日付を端末に印刷します。 - プロセス2が終了し、パイプの端が閉じられる。
- プロセス1が実行されます
sleep 4s
。 - 4秒後、プロセス1が実行され、
echo before hop2 >&2
「before hop2」が端末に印刷されます。 - プロセス1努力する実行が
echo hop2
パイプの唯一のリーダーがパイプを閉じたため、SIGPIPE エラーが発生します。これにより、明らかに完全なサブシェルプロセス(echo
コマンドだけでなく)が終了します。
これはecho
シェルが組み込まれているために発生します。/bin/echo hop2
(シェルの組み込みecho
コマンドではなく外部コマンド)を使用すると、sleep
期待どおりに2番目のコマンドが実行されます。
ちなみに、これは他のシェルで比較的一貫しています。 bash、zsh、dash、ksh(対話型)で同じ結果を得ました。スクリプトのkshは、続行する前にプロセス1が終了するのを待たないという点で若干異なります。したがって、すべてのdate
sがすぐに実行され、その後に一連の「hop2の前」行(4秒後)が続きます。
答え2
(echo ${test}; ...) | date
ここで何をしたいですか?データは標準入力に転送されていますが、date
入力date
を読みません。日付のみ印刷して終了します。
終了後、date
パイプが閉じて、後で印刷されたすべてのデータにはもう行きません。パイプ(サブシェル(echo; sleep; echo; sleep)
)に書き込むプロセスはSIGPIPE信号を受信して終了します。
これはパイプラインが一般的に機能する方法です。左側が大量の出力を生成できる場合、信号は右側が興味を失った後に停止するように指示することです。
たとえば、同様の状況では、cat /dev/zero | head -c128 > /dev/null
信号は最終的に終了し、cat
無期限に維持されません。シェルもシェルもcat
エラーメッセージを出力しません。このように動作するパイプは通常の動作の一部に過ぎません。今の状況も同じだ。 (場合によってはエラーメッセージが表示されますが、常にエラーメッセージが表示されるとは思わないでください。)
(外部ループは結果に影響を与えないため、省略しました。これを使用してtime ( ... ) | ...
パイプラインにかかる時間を測定できます。)
Bashでは、配列のパイプラインにあるコマンドの終了ステータスを確認できますPIPESTATUS
。
$ ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
date; declare -p PIPESTATUS
Sat Apr 10 21:34:56 EEST 2021
declare -a PIPESTATUS=([0]="141" [1]="0")
SIGPIPEは少なくともLinuxでは13回なので、表示されている終了ステータス141 = 128 +のシグナル番号と一致します。 (プロセスはステータス> = 128で正常に終了する可能性がありますが、ここではそうではありません。)
あるいは、strace
何が起こっているのかを確認できます。
$ strace -f bash -c '( echo ${test}; sleep 4s; echo hop2;
sleep 50s; echo hop3; ) | date'
...
[pid 31647] write(1, "hop2\n", 5) = -1 EPIPE (Broken pipe)
[pid 31647] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=31647, si_uid=1000} ---
[pid 31647] +++ killed by SIGPIPE +++
...
一方、パイプの右側にある入力を読み取ると、SIGPIPEはなく、パイプ全体が合計54秒間スリープモードになります。
$ time ( echo ${test}; sleep 4s; echo hop2; sleep 50s; echo hop3; ) |
cat > /dev/null
real 0m54.005s
user 0m0.000s
sys 0m0.000s
または、書き込み側でSIGPIPEを無視する場合:
$ time ( trap '' PIPE; echo foo; sleep 4s; echo hop2; sleep 10s;
echo hop3; ) | date
Sat Apr 10 21:57:07 EEST 2021
bash: echo: write error: Broken pipe
bash: echo: write error: Broken pipe
real 0m14.006s
user 0m0.004s
sys 0m0.000s
SIGPIPEが無視されると、シェルは閉じたパイプに書き込むと定期的にエラーを返し、この目的でエラーが印刷されます。
左側のパイプへの書き込みと右側のパイプのクローズの間にはタイミングの問題もあります。
私が次のようにした場合:
( echo foo; echo bar; sleep 4s; ) | date;
それはsleep
逃げた。ただし、両方を交換すると、LHSが到達する前に終了しecho
ます。 (ここでの競争はシステムによって異なります。)for i in {1..100}; do echo foo; done;
sleep
そして、date
LHSで異なるケースを最初に検討してください。
(date; echo ${test}; sleep 50s; echo hop2; sleep 50s; echo hop3) | date
これもタイミング問題のためだ。date
外部コマンドであるため、これを実行するシェルは内部で処理するよりも遅くなる可能性がありますecho
(最終的にはすべてのシェルの隣に組み込まれたコマンドです)。これにより、date
右側がレースで勝利し、最初の書き込み前にパイプを閉じる可能性が高くなりますecho
。
一般的に言えば、パイプに書き込むことは、一部のデータを相手に渡すことが目的ではなく、他のことがある場合に可能なSIGPIPEを処理する必要がある場合にはあまり役に立ちません。
答え3
date
標準入力でデータを受信していないため、送信するものはありません。
したがって、aを使用する|
ことは完全に間違っています。私はあなたが使うべきだと思います&&
。
しかし、問題は次のとおりです。
各出力間の時間間隔は何ですか?4秒変える八?
それでも動作しますが、解釈するのは混乱しています。つまり、後続のコマンドecho hop2
は実行されません。 2番目の睡眠は使用されませんでした。
$ (echo test; sleep 4s; echo hop2; sleep 4s; echo hop3) | date; date
Sun 11 Apr 2021 05:30:28 AM UTC
Sun 11 Apr 2021 05:30:32 AM UTC
stderr
代わりに、以下stdout
を使用してすべてのコマンドを実行する場合:
$ (echo test >&2; sleep 4s; echo hop2 >&2; sleep 4s; echo hop3 >&2) | date; date
test
Sun 11 Apr 2021 05:32:36 AM UTC
hop2
hop3
Sun 11 Apr 2021 05:32:44 AM UTC
その理由は、date
stdinから入力を受け取らず、ここに書き込もうとするとSIGPIPE応答を受け取り、全体(bash)シェル(左側のもの|
)が閉じられるからです。リスト内の他のすべてのコマンドを削除します。
例では、最初の項目が成功した理由はecho
時間によって異なります。
- 最初は、実行中のシェルがパイプを設定します
|
。 date
これを行うには、launchコマンドのサブコマンドを開きます。- 何も待たずに、シェルはパイプのもう一方の端に戻り(他のサブプロセスで)実行を要求します
echo ${test}
。 - パイプがまだ開いているため(
date
閉じていない)、echo出力がバッファリングされ、コマンドがsleep 4s
実行されます。 - この
date
コマンドには、パイプを完了して閉じるのに十分な時間があります。 - パイプが閉じていると、次のパイプ
echo
に書き込むことができません。 SIGPIPEが発生します。 - SIGPIPEを受け取った後、左側のサブシェルは
|
きちんと終了します(エコーが組み込まれており、組み込みエラーが原因でシェル全体が失敗します)。 - パイプからコマンドは実行されなくなりました。
だから:
$ (sleep 1; echo test; sleep 4s; echo hop2 >&2; ) | date ; date
Sun 11 Apr 2021 06:10:04 AM UTC
Sun 11 Apr 2021 06:10:05 AM UTC
最初のエコーも印刷しません。時間は0.1秒程度と短くできます。それは閉鎖を可能にするのに十分でしたdate
。
echo
遅延するのに十分な外部コマンドへの呼び出しでもあります。
(/usr/bin/true; echo test; sleep 4s; echo confirm >&2 ) | date; date
Sun 11 Apr 2021 06:27:01 AM UTC
Sun 11 Apr 2021 06:27:01 AM UTC
ループfor
は上記を3回繰り返します。したがって、各ループ間にはわずか4秒しかありません。