当管道终止到子命令时,为什么bash while循环不退出?


12

为什么下面的命令不退出?循环不是无限运行,而不是退出。

尽管我使用更复杂的设置发现了此行为,但命令的最简单形式简化为以下形式。

不退出:

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

上面没有错别字。每个“ |” 是管道。“出口1”代表另一个已运行并已退出的过程。

我期望“退出1”会在while循环上导致SIGPIPE(在没有读取器的管道上进行写操作),并使循环中断。但是,循环继续运行。

为什么命令不停止?


zsh正常退出。
Braiam 2014年

Answers:


13

这是由于实施中的选择。

在Solaris上与一起运行相同的脚本ksh93会产生不同的行为:

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

触发该问题的是内部管道,如果没有内部管道,则无论shell / OS是什么,循环都会退出:

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

cat 在bash下获取SIGPIPE信号,但是shell无论如何都在循环。

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文档指出:

在返回值之前,shell 等待管道中的所有命令终止。

Ksh文档指出:

除最后一条命令外,每个命令都作为单独的进程运行;Shell 等待最后一条命令终止。

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。因此,这是一个称为“ tens”的人为脚本:

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

当以方式运行时,这会正确终止tens | head。感谢上帝。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.