通常paste
打印二相邻列中的命名(或等效)文件如下所示:
paste <(printf '%s\n' a b) <(seq 2)
输出:
a 1
b 2
但是当两个文件是/dev/stdin
和时/dev/stderr
,它的工作方式似乎并不相同。
假设我们有b缺少b盒子输出两行的程序标准输出和两行标准误。出于说明目的,可以使用函数来模拟:
bb() { seq 2 | tee >(sed 's/^/e/' > /dev/stderr) ; }
现在运行annotate-output
, (在里面开发脚本封装在Debian/Ubuntu/等。),以表明它有效:
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
意味着按下控制-C退出。
将 更改|
为 a;
也不起作用:
bb ; paste /dev/stdin /dev/stderr
输出:
1
2
e1
e2
^C
也挂起——^C
意味着按下控制-C退出。
期望的输出:
1 e1
2 e2
可以使用 来完成吗paste
?如果没有,为什么不呢?
答案1
为什么不能使用 /dev/stderr 作为管道
问题不在于paste
,也不在于/dev/stdin
。它与/dev/stderr
.
所有命令均使用一个开放输入描述符(0:标准输入)和两个输出(1:标准输出和 2:标准错误)创建。这些通常可以分别使用名称/dev/stdin
、/dev/stdout
和/dev/stderr
来访问,但请参阅/dev/stdin、/dev/stdout 和 /dev/stderr 的可移植性如何?。许多命令(包括paste
)也会将文件名解释-
为 STDIN。
当您bb
单独运行时,STDOUT 和 STDERR 都是控制台,通常会出现命令输出。这些行经过不同的描述符(如您的 所示annotate-output
),但最终到达相同的位置。
当您添加一个|
和第二个命令时,创建管道......
bb | paste /dev/stdin /dev/stderr
告诉|
shell 将 的输出连接bb
到 的输入paste
。 paste
首先尝试读取/dev/stdin
,它(通过一些符号链接)解析为它自己的标准输入描述符(外壳刚刚连接起来),以便该行1
通过。
但 shell/pipeline 对 STDERR 没有任何作用。 bb
仍然将其(e1
e2
等)发送到控制台。同时,paste
尝试从同一个控制台读取数据,该控制台会挂起(直到您键入某些内容)。
你的链接为什么我无法使用文本编辑器读取 /dev/stdout?在这里仍然相关,因为这些相同的限制适用于/dev/stderr
.
如何制作第二条管道
您有一个命令可以生成标准输出和标准错误,并且您希望paste
这两行彼此相邻。这意味着两个并发管道,每一列一个。 shell 管道... | ...
提供其中之一,您需要自己创建第二个,并使用 .redirect 将 STDERR 重定向到该管道2>filename
。
mkfifo RHS
bb 2>RHS | paste /dev/stdin RHS
如果这是在脚本中使用,您可能更愿意将该 FIFO 放在临时目录中,并在使用后将其删除。
答案2
annotate-output
paste
之所以能够做到这一点,是因为它正在做一些特殊的事情(即将命令的 stderr 重定向到 fifo),绝对地没办法——只是paste
因为不是运行它从中获取输入的命令,并且它无法重定向它们的输入或输出。
但是您可以编写一个包装器,使用与 annotate-output 所使用的完全相同的技巧:
pasteout(){
f=$(mktemp -u) || return
mkfifo -m 600 -- "$f" || return
"$@" 2>"$f" | paste -- - "$f"
rm -f -- "$f"
}
pasteout bb
但请注意,它很容易出现死锁。例如,如果bb
产生的标准输出多于管道所能容纳的数量加上最初读取的额外数量,paste
但不会产生任何错误输出,paste
则将被阻止等待 fifo 上的输入,并且不会清空bb
正在将其标准输出提供给的管道,导致bb
管道的 write() 也挂起。
答案3
整条生产线有几个问题需要我们分析,那就是:
seq 2 | tee >(sed 's/^/e/' > /dev/stderr) | paste /dev/stdin /dev/stderr
标准错误
首先是最后一个命令。仅有的标准输出可以通过管道:
$ seq2 | paste -
1
2
$ seq2 | paste - -
1 2
没有什么可读的stderr
:
$ seq 2 | paste - /dev/stderr
1 ^C
您需要^C
它,因为它会阻塞,没有任何内容可供读取stderr
。
即使您创建一些输出,stderr
它也不会通过管道传输:
$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr
1 3
4
和以前一样,1
被打印并且paste
块等待stderr
。
另外 2 个数字直接进入控制台并(独立)打印。
stderr
您可以在管道的最后一个命令中提供一些输入:
$ { seq 2; seq 3 4 >/dev/stderr; } | paste - /dev/stderr 2</dev/null
1
2
3
4
2>/dev/null
顺便说一下,这与避免阻塞命令中使用的第二个文件描述符完全相同paste
。但打印的值直接来自seq 3 4
重定向到控制台,而不是来自paste
.这也有同样的作用:
$ { seq 2; seq 3 4 >/dev/tty; } | paste - /dev/stderr 2</dev/null
1
2
3
4
这并不会阻止:
$ seq 2 | tee >(sed 's/^/e/' > /dev/stderr) |
paste /dev/stdin /dev/stderr 2</dev/null
1
2
e1
e2
命令
其次, 的输出tee
不必是“按顺序”的。`tee` 和 `bash` 进程替换顺序
而且,事实上:进程替换的输出不必是“按顺序”的: 进程替换输出乱序
$ echo one; echo two > >(cat); echo three;
one
three
two
事实上,在某些示例中,如果您尝试多次,您可能会得到不同的订单。通过进程替换同时运行的独立进程的非确定性输出
$ printf '%s\n' {0..1000} | tee >(head -n2) >(sort -grk1,1 | head -n3) >/dev/null
1000
999
998
0
1
所以,不,不能通过过程替换和粘贴来完成。
您需要给执行一些命令:
$ seq 2 | { while read a; do printf "%s %s\n" "$a" "e$a" ; done; }
1 e1
2 e2
BB
所以,你的 bb 函数(基本上)包含:
| tee >(sed 's/^/e/')
可以用以下方法进行测试:
$ printf '%s\n' {0..1000} | tee >(sort -grk1,1 | head -n3 >&2) | head -n 2
0
1
291
290
289
应该按这个顺序打印 0, 1, 1000, 999, 998,但很多时候却没有。
即:是本质上不稳定。
稳定的实数解。
对于 bb 来说,唯一安全的解决方案是避免任何进程替换。
并且,利用{…}
捕获 stdout 和 stderr 的优势,例如:
$ bash -c '{ echo test-str >/dev/stderr; }' 2>/dev/null
无输出,去掉2确认。
这适用于 bb:
$ bb() { seq 5 | tee /dev/stderr | sed 's/^/e/'; }
并使用 fifo 进行粘贴:
$ mkfifo out2
$ bb 2>out2 | paste out2 -
1 e1
2 e2
3 e3
4 e4
5 e5
您需要设置一个陷阱来删除 fifo 文件,并在创建之前测试 fifo 文件是否存在。
似乎可以在我测试的所有 shell 上移植(与 Almquist 语法兼容)。尚未完全测试,请其他用户确认,可能会有一些未知的惊喜。