使用bash进程替换,我想同时在文件上运行两个不同的命令.在这个例子中,没有必要,但想象"cat/usr/share/dict/words"是一个非常昂贵的操作,例如解压缩50gb文件.
cat /usr/share/dict/words | tee >(head -1 > h.txt) >(tail -1 > t.txt) > /dev/null
在这个命令之后我希望h.txt包含单词文件"A"的第一行,并且t.txt包含文件"Zyzzogeton"的最后一行.
然而实际发生的是h.txt包含"A"但是t.txt包含"argillaceo",它大约是文件的5%.
为什么会这样?似乎要么"尾巴"进程提前终止,要么流正在混淆.
像这样运行另一个类似命令的行为符合预期:
cat /usr/share/dict/words | tee >(grep ^a > a.txt) >(grep ^z > z.txt) > /dev/null
在这个命令之后,我希望a.txt包含所有以"a"开头的单词,而z.txt包含所有以"z"开头的单词,这正是发生的事情.
那么为什么这不适用于"尾巴",以及其他命令会不起作用?
好吧,似乎发生的事情是,一旦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
因此,在所有内容退出之前,它只有一次循环迭代(尽管t.txt
仍然只有1
它).如果我们那么做
echo "${PIPESTATUS[@]}"
我们看
141 141
这这个问题关系到SIGPIPE
在一个非常类似的方式,我们现在看到的是什么.
coreutils维护者将此作为他们未来后代的tee
"陷阱"的一个例子.
有关如何符合POSIX合规性的开发人员的讨论,您可以在http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22195上看到(关闭的notabug)报告.
如果你有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