为什么在bash管道上使用`yes'不会*导致无限循环?


16

根据其文档,bash会一直等到管道中的所有命令运行完毕,然后再继续

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

那么,为什么命令yes | true立即完成?不应yes永远循环并导致管道永不返回吗?


还有一个子问题:根据POSIX规范,shell管道可以选择在最后一条命令完成之后返回,或者等待直到所有命令完成。从这个意义上说,普通的壳有不同的行为吗?是否有任何壳yes | true会永远循环?


yes | tee >(true) >/dev/null顺便说一句,它将继续如您所愿,tee直到所有作者都死了,所以true退出不会完全破坏它。
Charles Duffy 2015年

1
true基本上是一个{return 0;}程序,所以我不希望它运行很长时间,更不用说永远运行了。
德米特里·格里戈里耶夫

Answers:


33

如果true退出,该管的读取端是封闭的,而是yes继续尝试写入到写边。这种情况称为“中断管道”,它导致内核向发送SIGPIPE信号yes。由于yes对此信号无特殊要求,它将被杀死。如果忽略该信号,则其write调用将失败,并显示错误代码EPIPE。必须这样做的程序必须注意EPIPE并停止编写,否则它们将陷入无限循环。

如果执行strace yes | true1,则可以看到内核为两种可能性做准备:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

strace正在通过调试器API监视事件,该API首先告诉它有关系统调用返回错误的信息,然后是有关信号的信息。yes但是,从的角度来看,信号首先发生。(从技术上讲,信号是在内核将控制权返回给用户空间之后但在执行更多机器指令之前传递的,因此writeC库中的“包装器”函数没有机会设置errno并返回到应用程序。)


1可悲的是,strace它特定于Linux。大多数现代Unix都有一些执行类似操作的命令,但是它通常具有不同的名称,它可能无法彻底解码syscall参数,有时它仅适用于root用户。


3
在这种情况下,@ hugomg与管道完全无关。
muru

3
@hugomg,因为没有任何东西yes连接到管道。
muru

4
的确,这是对所记录行为“等待所有命令完成然后终止管道”的证明。这只是防止yes获取SIGPIPE,因为它正在写入的FD未连接到管道。
汤姆·亨特

2
@hugomg,它就像yes >/dev/null永远循环一样永远循环。它根本没有说明简单命令也不适用的所有管道(因为汤姆指出的等待终止行为也适用于简单命令)。
查尔斯·达菲

2
@zwol:我认为我们在这里使用的术语含义略有不同,或者从稍微不同的角度思考问题……但是,无论哪种情况,write()(libc中的函数)都不会返回(将控制权转移到紧随其后的PC上)信号处理程序已运行,但是由于信号处理程序终止了程序,因此控制权永远不会转移,因此write()永远不会返回。是的,这是通过具有一些xxx_write()return函数在内核中实现的-EPIPE,但是我们正在调试用户空间程序,对此不感兴趣。
Dietrich Epp 2015年

5

是否有贝壳?真的会永远循环吗?

不太可能,因为该yes命令正在使用管道,并且在管道断开时它将失败。sleep另一方面,不使用管道,因此:

sleep 100000000 | true

至少会运行100000000秒。


2
请注意所有不会在管道中执行最后一个(最右边的)内置命令的现代外壳,以及true内置的位置。这适用于最新版本的Bourne Shellksh93zsh。如果您^Z在运行这样的命令时命中,则会暂停睡眠,并且如果没有外部帮助,外壳将永远无法恢复。
2015年

3
zsh 4.3.4(i386-pc-solaris2.11)在这里,因此看来这是最近修改的。有趣的想法,我需要看看是否可以为Bourne Shell实施类似的修复程序。仍然存在一个问题,它是如何工作的,以及在Bourne Shell中使用哪个tty进程组,就已经永久设置了睡眠的进程组,从而发现了righmost命令是内置命令这一事实。
schily 2015年

2
据我所知,@ CharlesDuffy schily维护了sh的一个版本,他向其移植了现代shell的改进。他在这里某处发布了有关此事的信息。
muru

3
传家宝档案中的Bourne Shell一直保留到2007年左右,但由于它仍然包含对sbrk()。schily工具包中包含一个可移植且维护的版本,@ Charles Duffy已经发现了一个信息
存放

2
@muru我向后移植到Bourne Shell的许多功能都来自我bsh(来自VBERTOS的Berthold Shell,这是UNOS的虚拟内存增强版本-第一个UNIX克隆)。Bsh在1984年和1985年确实获得了许多csh功能,但是UNOS的别名机制已经优于1980年csh的别名机制。Bourne Shell的其他新功能来自POSIX,以使其符合POSIX规范。
schily
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.