为什么在 bash 管道上使用“是”*不会*导致无限循环?

hug*_*omg 19 shell bash pipe

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

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

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


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

cas*_*sey 36

如果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 +++
Run Code Online (Sandbox Code Playgroud)

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


1遗憾的是,这strace是 Linux 特有的。大多数现代 Unix 都有一些执行类似操作的命令,但它通常具有不同的名称,它可能不会彻底解码系统调用参数,有时它仅适用于 root。

  • 确实,这是对记录的行为的演示“在终止管道之前等待所有命令完成”。它只是阻止“是”获得 SIGPIPE,因为它写入的 FD 未连接到管道。 (4认同)
  • @hugomg 在这种情况下,管道完全无关紧要。 (3认同)
  • @hugomg 因为 `yes` 中没有任何内容连接到管道。 (3认同)
  • @hugomg,它永远循环,就像`yes >/dev/null` 永远循环一样。它根本没有展示任何与简单命令不同的管道(正如 Tom 指出的等待终止行为也适用于简单命令)。 (2认同)
  • @zwol:我认为我们在这里使用的术语含义略有不同,或者从略有不同的角度思考问题……但在任何一种情况下,`write()`(libc 中的函数)都不会返回(将控制权转移到 PC跟随它)直到信号处理程序运行之后,但由于信号处理程序终止了程序,控制权永远不会转移,因此“write()”永远不会返回。是的,这是通过让一些 `xxx_write()` 函数返回 `-EPIPE` 在内核中实现的,但是我们正在调试一个用户空间程序并且对此不感兴趣。 (2认同)

mur*_*uru 5

有没有贝壳?true 会永远循环吗?

不太可能,因为yes命令正在使用管道,当管道损坏时它会失败。sleep另一方面,不使用管道,所以:

sleep 100000000 | true
Run Code Online (Sandbox Code Playgroud)

至少会运行 100000000 秒。

  • zsh 4.3.4 (i386-pc-solaris2.11) 在这里,所以这似乎是最近修改的。有趣的想法,我需要看看我是否可以为 Bourne Shell 实现类似的修复。仍然存在一个问题,它是如何工作的,以及在 Bourne Shell 中使用哪个 tty 进程组,最右边的命令是内置命令是在已经永久设置睡眠的进程组之后发现的。 (3认同)
  • 传家宝档案中的 Bourne Shell 一直维护到 ~ 2007 年,但从未完全可移植,因为它仍然包含对 `sbrk()` 的调用。可移植和维护的版本在 schily 工具包中,@Charles Duffy 已经发现了一个信息位置;-) (3认同)
  • 小心所有现代 shell,它们不会为管道中的最后一个(最右边的)内置命令分叉,并且 `true` 是一个内置命令。这适用于“Bourne Shell”、“ksh93”、“zsh”的最新版本。如果您在运行这样的命令时点击“^Z”,这将暂停睡眠,并且在没有外部帮助的情况下,shell 将永远无法恢复。 (2认同)
  • @CharlesDuffy 据我所知,schily 维护了一个 sh 版本,他将现代 shell 的改进移植到了该版本中。他在这里,某处发布了关于它的信息。 (2认同)
  • @muru 我向后移植到 Bourne Shell 的许多功能都来自我的 `bsh`(来自 VBERTOS 的 Berthold Shell,UNOS 的虚拟内存增强版本 - 第一个 UNIX 克隆)。Bsh 在 1984 年和 1985 年确实获得了许多 csh 功能,但是 UNOS 的别名机制已经优于 1980 年来自 csh 的机制。其他新的 Bourne Shell 功能来自 POSIX,以使其接近 POSIX 合规性。 (2认同)