为什么下面的命令不退出?循环不退出,而是无限运行。
虽然我使用更复杂的设置发现了这种行为,但该命令的最简单形式简化为以下内容。
不退出:
while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
上面没有拼写错误。每个“|”都是一个管道。“exit 1”代表另一个运行并退出的进程。
我期望“exit 1”会在 while 循环中引发 SIGPIPE(在没有读取器的管道上写入)并跳出循环。但是,循环仍在继续运行。
为什么命令不停止?
答案1
这是由于实施中的选择。
在 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) ---
猛击文档指出:
贝壳等待所有命令在管道返回值之前终止。
肯尼亚文档指出:
每个命令(可能最后一个命令除外)都作为单独的进程运行;shell等待最后一条命令终止。
POSIX状态:
如果管道不在后台(参见异步列表),则 shell应等待最后一个命令指定要完成的管道,以及也可以等待所有命令去完成。
答案2
这个问题困扰了我好几年。感谢 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
已确认...
嗯,不完全是。下面是一些更复杂的事情:
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
。感谢上帝。
答案3
先决条件:https://askubuntu.com/a/678922
最好使用临时文件描述符和进程替换。这将按照您的意愿终止(请记住,罪魁祸首不是cat
)while
。
以下内容将永远停留
while true; do cat /dev/zero; done | head -c 5 | od
但是使用进程替换,我们可以杀死上游。
(head -c 5 | od) < <(while true; do cat /dev/zero; done)
答案到此结束
使用黑客手段(有风险,可能并不总是有效)
while cat /dev/zero; do true; done | head -c 5 | od
观众需要理解如何cat
接收SIGPIPE
信号,并且true
不会产生干扰(提示:如果使用有限流而不是,可能会产生干扰/dev/zero
)
未来的调查
# what about this?
while cat finite_file; do cat finite_file; done | head -c 5 | od