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


11

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

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

終了しません:

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

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

「exit 1」により、whileループ(リーダーのないパイプに書き込み)でSIGPIPEが発生し、ループが発生することが予想されます。しかし、ループは実行を続けます。

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


zshは正常に終了します。
-Braiam

回答:


13

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

Solaris上で同じスクリプトを実行するとksh93、異なる動作が生成されます。

$ 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状態:

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


私はそれが問題を引き起こす正確な内部パイプラインではなく、組み込みechoがSIGPIPEを無視することだと思います。env echo代わりにecho(実際のechoバイナリを強制的に使用する)を使用して問題を再現することもできます。({ echo hi; echo $? >&2; } | exit 1との出力も比較してください{ env echo hi; echo $? >&2; } | exit 1。)
ルーカスヴェルクマイスター

1

この問題は長年私を悩ませてきました。正しい方向へのナッジのためのjilliagreに感謝します。

私のLinuxボックスでは、質問を少し言い直しますが、これは予想通りに終了します:

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

しかし、パイプを追加して、期待どおりに終了しませ

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

それは何年もイライラさせられました。jilliagreによって書かれた答えを検討することで、このすばらしい修正を思いつきました。

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

QED ...

まあ、そうではありません。もう少し複雑なものを次に示します。

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

これは正しく機能しません。私は|| exitそれを追加したので、それは早期に終了する方法を知っていますが、最初のechoものは一致しないgrepので、ループはすぐに終了します。この場合、あなたは本当にの終了ステータスに興味がありませんgrep。私の回避策は、別のものを追加することcatです。そのため、ここには「10」と呼ばれる人為的なスクリプトがあります。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.