好的,看起来发生的情况是,一旦head -1
命令完成并退出,这将导致tee
收到一个SIGPIPE,它试图写入进程替换设置的命名管道,该替换生成一个EPIPE
,根据man 2 write
,在写入过程中也会生成SIGPIPE
,这会导致tee
退出,强制tail -1
立即退出,并且左侧的cat
也会得到一个SIGPIPE
。
如果我们使用head
添加一些更多的进程并使输出更可预测并且不依赖于tee
来写入stderr
,就可以更清楚地看到这一点:
for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null
当我运行它时,它给了我输出:
1
Head done
2
在一切退出之前,循环只执行了1次(尽管t.txt
仍然只有1
)。如果我们接下来执行
echo "${PIPESTATUS[@]}"
我们看到
141 141
这个问题与SIGPIPE
有着非常类似的联系,这个问题详细解释了这一点。
coreutils
维护者已将此作为示例添加到其tee
“陷阱”中以备将来参考。
如果想要了解开发人员关于如何符合POSIX标准的讨论,可以查看http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195中的(关闭的无效)报告。
如果您有GNU版本8.24的访问权限,则可以使用一些选项(不在POSIX中),例如-p
或--output-error=warn
,它们可以提供帮助。否则,您可以冒一点风险通过捕获并忽略SIGPIPE来得到所需的功能:
trap '' PIPE
for i in {1..30}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done") >(tail -1 > t.txt) >/dev/null
trap - PIPE
在 h.txt
和 t.txt
中都会产生预期的结果,但如果发生了其他需要正确处理 SIGPIPE 的情况,则使用此方法将没有运气。
另一个hacky选项是在开始之前将t.txt
归零,然后不让head
进程列表完成,直到其长度为非零:
> t.txt; for i in {1..10}; do echo "$i"; echo "$i" >&2; sleep 1; done | tee >(head -1 > h.txt; echo "Head done"; while [ ! -s t.txt ]; do sleep 1; done) >(tail -1 > t.txt; date) >/dev/null