为什么Tee命令无法将输出写入标准输出?

3

我通常使用 tee 接收管道输出数据并将其回显到标准输出,然后转发到管道数据的实际接收者。但有时会失败,我无法确切地理解原因。

我将试着用一系列示例来演示:

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

因此,如果没有参数地将一些数据回声到tee上,它将在终端/标准输出上被复制/打印。请注意,这应该是tee打印输出,因为来自echo的输出现在已经被“管道”/重定向,因此不再存在于stdout中(这里发生的与此相同)。
$ echo aa | echo bb
bb

例如:echo aa的输出被重定向到下一个命令,该命令是echo b,它不关心输入,只输出自己的输出。


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

现在在这里,将数据无参数地导入tee,然后从tee管道传输到不向终端/标准输出提供任何输出的程序-什么也不打印。我本来希望tee在这里能够复制到标准输出,并转发到管道中的下一个命令,但显然这并没有发生。


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

如果我们在命令行参数/dev/stdout下使用管道符号和tee,我们将获得两次输出 - 正如先前得出的结论一样,必须是tee产生了这两行打印。这意味着,当没有给它传递参数时,|tee基本上不会打开任何文件进行复制,而只是将其输入接收到的内容转发到其输出; 但由于它是管道中的最后一个命令,所以在这种情况下,它的输出是标准输出,因此我们只得到一次输出。

这里我们得到了双重输出,因为:

  • tee由于参数而将其输入流复制到/dev/stdout(成为第一次输出);然后
  • 将相同的输入转发到其输出,这里的输出结果是标准输出(因为tee再次成为管道中的最后一个命令),从而产生第二个输出。

这也可以解释为什么之前的...| tee | python3 -c 'a=1'没有输出任何内容:没有给tee传递参数,因此没有打开任何文件进行复制,仅仅是将输入转发到工具链中的下一个命令 - 由于下一个命令也没有产生任何输出,因此根本没有生成任何输出。


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

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

... 应该 至少打印一行(从 tee 复制到 /dev/stdout ;“转发”部分最终会被最后一个命令“吞掉”,因为它没有打印任何东西),但实际上没有

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

以及如何使用 tee 将输出打印到 stdout,即使其输出被转发到不会自行打印任何内容的命令?


请注意,没有任何参数的 tee 是无用的:它只是将标准输入复制到标准输出。如果您从管道中删除 tee,则会得到相同的行为。 - user1934428
2个回答

4
你没有误解 `tee` 命令,而是误解了标准输出(stdout)的含义。在一个管道(pipe)中,比如说 `echo testing | tee | python3 -c 'a=1'`,`tee` 命令的标准输出并不是终端,而是指向 `python` 命令的管道(同时 `echo` 命令的标准输出则指向 `tee` 的管道)。
因此,`tee /dev/stdout` 将其输入(stdin)的两份拷贝都发送到同一个地方:即它的标准输出(stdout),不论是终端、管道或其他地方都一样。
如果你想将输入的拷贝发送到除管道以外的地方,那么你需要将其发送到标准输出以外的地方。具体要发送到哪里取决于你想复制它的原因。比如,如果你想把它发送到终端上,可以这样做:
echo testing | tee /dev/tty | python3 -c 'a=1'

如果你想将输出发送到外部上下文的标准输出(可能是终端,也可能不是),你可以复制外部上下文的标准输入到另一个文件描述符中(#3很方便),然后使用tee将其副本写入该描述符:

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

另外一个选项是将其重定向到stderr(也就是FD #2,默认情况下也是终端,但是可以与stdout分开重定向),使用tee /dev/fd/2

请注意,我在这里使用的各种/dev条目受到大多数Unixish操作系统的支持,但它们并不是普遍存在的。请检查您特定的操作系统提供了哪些内容。


太棒了 - 非常感谢 @GordonDavisson;"... tee /dev/stdout 将其输入(在 stdin 上)的两个副本发送到完全相同的位置:它的 stdout",这为我澄清了一切! - sdbbs

0

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

所以,如果管道会创建子shell,那么

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

概念上等同于:

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

请注意,第二个管道符 | 重定向了子shell中运行的tee的标准输出。由于/dev/stdout只是标准输出的接口,因此它也被重定向了,所以我们没有得到任何输出。
因此,虽然标准输出(和/dev/stdout)是局部于(子)shell的,但/dev/tty是局部于终端的 - 因此如下:
$ echo testing with this string | tee /dev/tty | python3 -c 'a=1'
testing with this string

实际上打印了一行,如预期。


1
这不太对——管道的元素在子进程中运行,但不一定在子shell中运行。像teepython这样的普通命令只是作为一个普通的子进程运行(其标准输入和/或输出被定向到相关的管道)。但如果命令是一个shell内置命令(如read)或其他shell结构(如while循环),那么运行它的子进程将是一个子shell。 - Gordon Davisson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接