当管道输出到三通时管道失败 (141) - 为什么?

当管道输出到三通时管道失败 (141) - 为什么?

一个例子应该可以澄清我的问题。这种行为对我来说很有意义:

$ echo hi | cat
hi
$ echo hi | tee >(cat)
hi
hi

第一种情况是显而易见的。在第二种情况下,我们使用命令替换将“hi”通过管道传输到 tee 中,一个“hi”由tee'd打印,而另一个“hi”由' 传递管道cat打印。tee到目前为止,一切都很好...

但是在这种情况下第一个“hi”会发生什么:

$ echo hi | tee >(echo yo)
yo

返回代码是 141,管道失败。可能是什么原因造成的?

我正在默认终端应用程序中运行 Mac OSX El Capitain、bash

答案1

我想我已经找到了如何调整您的体验,将其变成其他人能够重现的东西:

$ (回声你好;睡眠1;回声世界)| T 恤 >(猫)
你好
你好                                                       ......并且,在短暂的延迟之后,
世界
世界

$ 回显“$?”
0

$ (回声你好;睡眠1;回声世界)| T 恤 >(回声哟)
你好

$ 回显“$?”
141

正如您希望理解的那样, 创建一个通往正在运行的进程的管道>(command)command。标准输入为command连接到命令行上的其他命令(在本例中为tee)可以打开和写入的路径名。什么时候commandcat,该进程坐在那里并从 stdin 读取,直到获得 EOF。在这种情况下,tee将从标准输入读取的所有数据写入管道都没有问题。

但当commandecho yo,进程写入yo标准输出并立即退出。这会导致一个问题tee;当它写入另一端没有进程的管道时,它会收到 SIGPIPE 信号。

显然,OS X 的版本tee 首先在命令行上写入文件,然后是其标准输出。因此,在您的示例 ( echo hi | tee >(echo yo)) 中, tee在第一次写入时就出现管道故障。然而,Linux 和 Cygwin 上的版本tee写入标准输出第一的,因此它能够hi在死亡之前向屏幕写入内容。在我的增强示例中,tee当它写入hello管道时就会死亡,因此它没有机会读取和写入world.

答案2

要直观地了解正在发生的情况,请比较以下两种变体:

bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?'

bash -c 'wait_and_tee () { sleep 1; tee "$@"; };
         echo hi | wait_and_tee >(echo yo); echo $?'

注意到第一个变体中发生了什么吗?

$ bash -c 'echo hi | tee >(sleep 1; echo yo); echo $?'     
hi
0
$ yo

进程替换中的命令sleep 1; echo yo与外部的命令并行执行,bash 不会等待它完成。所以事件的顺序是:

  • echo hisleep 1; echo yo和三个命令tee并行启动。
  • echo hi写入hi其输出(|管道)。
  • tee从管道读取并写入其标准输出及其命令行参数,这是由>(…). 创建的另一个管道。这会导致一份副本hi打印到终端,一份副本保存在>(…)管道的缓冲区中。
  • 与前面的要点并行,echo hi退出,它关闭管道的写入端|
  • tee注意到它已到达输入文件的末尾。它已写出所有数据,因此退出。
  • 从bash的角度来看,管道两边都已经退出,所以命令结束了。由于tee返回0,管道的状态为0。
  • 一秒后,sleep 1结束,echo yo执行。

在第二种变体中,我通过强制它在开始之前终止来强制终止echo yobefore 。这次,事件的顺序是:teetee

  • echo hiecho yo和三个命令sleep 1并行启动。
  • echo hi写入hi其输出(|管道)。
  • 与前一个要点并行,echo yo打印yo并退出。
  • 一秒钟后,sleep 1退出并tee启动。
  • tee从其输入中读取hi,并尝试将其写入终端(其标准输出)和作为>(…)参数传递的管道。由于打开此管道进行读取的进程 ( echo yo) 一秒钟前退出,因此尝试写入管道失败,并显示信号管道(信号13,其中shell 报告为 128+signal_number)。

正如 G-Man 所解释的,是否hi在第二种情况下显示取决于是否tee尝试首先写入其标准输出或其文件参数。

如果没有sleep调用,计时可能会以任何一种方式进行(我在 Linux 下得到大约一半/一半,不同的内核可能会使一个计时比另一个计时更有可能)。

相关内容