通常在相邻列中paste
打印两个命名(或等效)文件,如下所示:
paste <(printf '%s\n' a b) <(seq 2)
输出:
a 1
b 2
但是当这两个文件是/dev/stdin
and/dev/stderr
时,它的工作方式似乎不同。
假设我们有b缺少b框程序,它在标准输出上输出两行,在标准错误上输出两行。为了说明的目的,这可以用一个函数来模拟:
bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }
现在运行annotate-output
,(在Debian/Ubuntu/etc.上的devscripts包中),以显示它可以工作:
annotate-output bash -c 'bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }; bb'
22:06:17 I: Started bash -c bb() { seq 2 | tee >(sed s/^/e/ > /dev/stderr) ; }; bb
22:06:17 O: 1
22:06:17 E: e1
22:06:17 O: 2
22:06:17 E: e2
22:06:17 I: Finished with exitcode 0
所以它有效。喂给:bb
_paste
bb | paste /dev/stdin /dev/stderr
输出:
1 e1
e2
^C
它挂起 -^C
意味着按Control-C退出。
更改|
为 a;
也不起作用:
bb ; paste /dev/stdin /dev/stderr
输出:
1
2
e1
e2
^C
也挂起 -^C
表示按Control-C退出。
期望的输出:
1 e1
2 e2
可以使用paste
吗?如果不是,为什么不呢?
为什么不能使用 /dev/stderr 作为管道
问题不
paste
在于 ,也不在于/dev/stdin
。它与/dev/stderr
.所有命令都使用一个打开的输入描述符(0:标准输入)和两个输出(1:标准输出和 2:标准错误)创建。这些通常可以分别使用 names和访问
/dev/stdin
,但请参阅/dev/stdin、/dev/stdout 和 /dev/stderr 的可移植性如何?. 许多命令,包括,也会将文件名解释为 STDIN。/dev/stdout
/dev/stderr
paste
-
当您单独运行
bb
时,STDOUT 和 STDERR 都是控制台,通常会出现命令输出。这些行通过不同的描述符(如您的 所示annotate-output
)但最终在同一个地方结束。当您添加一个
|
和第二个命令时,创建一个管道......|
告诉 shell 将 的输出连接到bb
的输入paste
。paste
首先尝试从 读取/dev/stdin
,它(通过一些符号链接)解析为它自己的标准输入描述符(shell 刚刚连接),因此该行1
通过。但是外壳/管道对 STDERR 没有任何作用。
bb
仍然将(e1
e2
等)发送到控制台。同时,paste
尝试从挂起的同一个控制台读取(直到您输入某些内容)。您的链接为什么我不能使用文本编辑器阅读 /dev/stdout?在这里仍然相关,因为同样的限制适用于
/dev/stderr
.如何制作第二个管道
您有一个同时产生标准输出和标准错误的命令,并且您希望
paste
这两行彼此相邻。这意味着两个并发管道,每列一个。shell 管道... | ...
提供了其中之一,您需要自己创建第二个,并将 STDERR 重定向到使用2>filename
.如果这是在脚本中使用,您可能更愿意在临时目录中创建该 FIFO,并在使用后将其删除。
annotate-output
能够做到这一点是因为它正在做一些特殊的事情(即将命令的标准错误重定向到先进先出),这是paste
绝对无法做到的事情——仅仅是因为paste
它没有运行它自己从它获取输入的命令,并且它无法重定向它们的输入或输出。但是您可以编写一个使用与注释输出完全相同的技巧的包装器:
但是请注意,它很容易出现死锁。例如,如果
bb
产生的标准输出超出了管道的容量,加上最初读取的额外量paste
但不产生任何错误输出,paste
则将被阻塞以等待 fifo 上的输入,并且不会清空bb
正在将其标准输出提供给的管道,导致bb
管道的 write()s 也挂起。整条线路有几个问题需要我们分析,即:
标准错误
首先,最后一个命令。只有stdout可以通过管道:
没有什么可读取的
stderr
:你需要
^C
它,因为它会阻塞,没有什么可以读取的stderr
。即使您创建一些输出,
stderr
它也不会通过管道:和以前一样,
1
打印出来的paste
块等待stderr
.其他 2 个数字直接进入控制台并(独立)打印出来。
您可以
stderr
在管道的最后一个命令中提供一些输入:这与顺便说一句完全相同
2>/dev/null
,以避免阻塞命令中使用的第二个文件描述符paste
。但是打印的值直接来自seq 3 4
重定向到控制台,而不是来自paste
. 这也是一样的:这不会阻止:
命令
其次, 的输出
tee
不必“按顺序”。`tee` 和 `bash` 进程替换顺序而且,事实上:流程替换的输出不必是“按顺序” 的:流程替换输出是无序的
事实上,在某些示例中,如果您尝试多次,您可能会得到不同的订单。通过进程替换同时运行的独立进程的非确定性输出
所以,不,它不能通过过程替换和粘贴来完成。
你需要给执行一些命令:
bb
因此,您的 bb 函数(基本上)包含:
可以通过以下方式进行测试:
应该按顺序打印 0、1、1000、999、998,但很多时候不是。
那就是:它本质上是不稳定的。
稳定的真实解决方案。
bb 唯一安全的解决方案是避免任何进程替换。
并且,利用
{…}
捕获 stdout 和 stderr 的优势,例如:没有输出,去掉2确认。
这将适用于 bb:
并使用 fifo 进行粘贴:
您需要设置一个陷阱来删除 fifo 文件,并在创建 fifo 文件之前测试它是否存在。
似乎可以在我测试的所有 shell(与 Almquist 语法兼容)上便携工作。未完全测试,请其他用户确认,可能会有一些未知的惊喜。