web-dev-qa-db-ja.com

終了したサブコマンドにパイプするときにbashwhileループが終了しないのはなぜですか?

以下のコマンドが終了しないのはなぜですか?ループは終了するのではなく、無期限に実行されます。

より複雑なセットアップを使用してこの動作を発見しましたが、コマンドの最も単純な形式は次のようになります。

終了しません:

while /usr/bin/true ; do echo "ok" | cat ; done | exit 1

上記のタイプミスはありません。各 '|'パイプです。 「exit1」は、実行されて終了した別のプロセスを表します。

「exit1」によってwhileループ(リーダーのないパイプに書き込む)でSIGPIPEが発生し、ループが発生することを期待しています。ただし、ループは実行を継続します。

コマンドが停止しないのはなぜですか?

12
stephen.z

これは、実装の選択によるものです。

ksh93を使用してSolarisで同じスクリプトを実行すると、異なる動作が発生します。

$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]

問題を引き起こすのは内部パイプラインです。それがないと、シェル/ OSが何であれループが終了します。

$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$

catはbashでSIGPIPEシグナルを取得していますが、シェルはとにかくループを繰り返しています。

Process 5659 suspended
[pid 28801] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28801] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28801 detached
Process 28800 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28802 attached
Process 28803 attached
[pid 28803] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
Process 5659 suspended
[pid 28803] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28803 detached
Process 28802 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28804 attached
Process 28805 attached (waiting for parent)
Process 28805 resumed (parent 5659 ready)
Process 5659 suspended
[pid 28805] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28805] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28805 detached
Process 28804 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

Bashドキュメントの状態:

シェルすべてのコマンドを待機パイプラインで終了してから値を返します。

Kshドキュメントの状態:

おそらく最後を除いて、各コマンドは個別のプロセスとして実行されます。シェル最後のコマンドを待つ終了します。

[〜#〜] posix [〜#〜]状態:

パイプラインがバックグラウンドにない場合(非同期リストを参照)、シェル最後のコマンドを待機する必要がありますパイプラインで指定された完了、およびすべてのコマンドを待機する場合もあります完了します。

13
jlliagre

この問題は何年もの間私を悩ませてきました。正しい方向にナッジしてくれたjiliagreに感謝します。

質問を少し言い換えると、私のLinuxボックスでは、これは期待どおりに終了します。

while true ; do echo "ok"; done | head

しかし、パイプを追加すると、期待どおりに終了しますnot

while true ; do echo "ok" | cat; done | head

それは何年もの間私を苛立たせました。 jilliagreによって書かれた答えを検討することによって、私はこの素晴らしい修正を思いつきました:

while true ; do echo "ok" | cat || exit; done | head

Q.E.D. .。

まあ、完全ではありません。これはもう少し複雑なものです:

i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' || exit
done | head

これは正しく機能しません。 || exitを追加したので、早期に終了する方法がわかりますが、最初のechogrepと一致しないため、ループはすぐに終了します。この場合、grepの終了ステータスには実際には関心がありません。私の回避策は、別のcatを追加することです。したがって、これは「tens」と呼ばれる不自然なスクリプトです。

#!/bin/bash
i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' | cat || exit
done

これは、tens | headとして実行すると適切に終了します。ああ、助かった。

1
PaulC