好吧,似乎发生的事情是一旦head -1
命令完成后退出,这会导致tee
为了获得 SIGPIPE,它尝试将进程替换设置写入命名管道,该管道生成一个EPIPE
并根据man 2 write
也会产生SIGPIPE
在写作过程中,导致tee
退出并迫使tail -1
立即退出,并且cat
左边有一个SIGPIPE
以及。
如果我们在流程中添加更多内容,我们可以更好地看到这一点head
并使输出更可预测并且也写入stderr
不依赖于tee
:
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[@]}"
we see
141 141
which 这个问题 https://stackoverflow.com/questions/19120263/why-exit-code-141-with-grep-q与SIGPIPE
以与我们在这里看到的非常相似的方式。
coreutils 维护者已将此作为示例添加到他们的tee“陷阱” http://www.pixelbeat.org/docs/coreutils-gotchas.html#tee为了未来的子孙后代。
要与开发人员讨论这如何符合 POSIX 合规性,您可以在以下位置查看(已关闭的 notabug)报告:http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195 http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195
如果您可以访问 GNU 版本 8.24,他们添加了一些可以提供帮助的选项(不在 POSIX 中),例如-p
or --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
and t.txt
,但是如果发生了其他需要正确处理 SIGPIPE 的事情,那么您将无法使用这种方法。
另一个黑客选项是归零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