为什么 tee 无法写入标准输出?

Why would tee fail to write to stdout?

我通常使用 tee 来接收管道输出数据,将其回显到标准输出,然后将其转发给管道数据的实际预期接收者。但有时这会失败,我不明白为什么。

我将尝试通过一系列示例进行演示:

$ echo testing with this string | tee
testing with this string

所以,只是将一些数据不带参数地回显到 tee,就是 terminal/stdout 上的 replicated/printed。请注意,这应该是 tee 打印输出,因为 echo 的输出现在是“管道”/重定向的,因此不再出现在标准输出中(与这里发生的相同:

$ echo aa | echo bb
bb

... 即 echo aa 输出被重定向到下一个命令,- echo b 不关心输入,只输出它自己的输出。)


$ echo testing with this string | tee | python3 -c 'a=1'
$

现在在这里,将数据不带参数地传送到 tee,- 然后从 tee 传送到不向 terminal/stdout 提供任何输出的程序 - 打印 没有。我原以为 tee 会复制到标准输出,然后转发到管道中的下一个命令,但显然这不会发生。


$ echo testing with this string | tee /dev/stdout 
testing with this string
testing with this string

是的,所以如果我们使用命令行参数 /dev/stdout 通过管道传输到 tee,我们会得到两次打印输出 - 如前所述,它必须是 tee 产生两行打印。这意味着,当不带参数使用时,|tee 基本上 而不是 打开任何文件进行复制,并简单地将其输入接收到的内容转发到输出;但由于它是管道中的最后一个,在这种情况下它的输出是标准输出,所以我们得到一个打印输出。

这里我们得到双重打印输出,因为

这也可以解释为什么之前的 ...| tee | python3 -c 'a=1' 没有打印任何东西:没有参数的 tee 没有打开任何文件进行复制,只是转发到工具链中的下一个命令 - 作为下一个也不打印任何输出,没有任何输出生成。


嗯,如果上面的理解是正确的,那么这个:

$ echo testing with this string | tee /dev/stdout | python3 -c 'a=1'
$

... 应该 打印至少一行(从 tee 复制到 /dev/stdout;“转发”部分最终将被“吞噬” " 由最终命令执行,因为它什么都不打印),但确实 not.

那么,为什么会发生这种情况 - 我对 tee 的理解哪里出错了?

我如何使用 tee 来打印到标准输出,当它的输出被转发到一个本身不打印任何东西到标准输出的命令时?

你没有误解tee,你误解了什么是标准输出。在管道中,例如 echo testing | tee | python3 -c 'a=1'tee 命令的标准输出不是终端,它是通往 python 命令的管道(而 echo 命令的标准输出是通往终端的管道至 tee).

因此 tee /dev/stdout 将其输入(在 stdin 上)的两个副本发送到完全相同的位置:它的 stdout,无论是终端、管道还是其他任何地方。

如果您想将输入的副本发送到 tee 管道以外的某个地方,您需要将它发送到 stdout 以外的某个地方。那在哪里取决于你实际想要发送它的地方(即你为什么要复制它)。如果你特别想把它发送到终端,你可以这样做:

echo testing | tee /dev/tty | python3 -c 'a=1'

...虽然如果你想将它发送到外部上下文的标准输出(它可能是也可能不是终端),你可以将外部上下文的标准输入复制到不同的文件描述符(#3 对此很方便),然后让 tee 向其写入副本:

{ echo testing | tee /dev/fd/3 | python3 -c 'a=1'; } 3>&1

另一种选择是使用 tee /dev/fd/2.

将其重定向到 stderr(又名 FD #2,默认情况下也是终端,但可与 stdout 分开重定向)

请注意,大多数 unixish OSes 都支持我在这里使用的各种 /dev 条目,但它们并不通用。查看您的特定 OS 提供的内容。

我想我明白了,但不确定它是否正确:我看到了这个:19.8. Forgetting That Pipelines Make Subshells - bash Cookbook [Book]

因此,如果管道生成 subshells,那么

echo testing with this string | tee /dev/stdout | python3 -c 'a=1'

... 在概念上等于:

echo testing with this string | (tee /dev/stdout | (python3 -c 'a=1'))

请注意,第二个管道 | 重定向 stdout of the subshell tee runs in, and as /dev/stdout只是 stdout 的接口,它也被重定向,所以我们得到 nothing 打印。

因此,虽然 stdout(和 /dev/stdout)对于 (sub)shell 是本地的,但 /dev/tty 对于 终端 - 因此如下:

$ echo testing with this string | tee /dev/tty | python3 -c 'a=1'
testing with this string

...实际上如预期的那样打印了一行。