bash 进程替换和尾部的结果不正确?
Incorrect results with bash process substitution and tail?
使用 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%。
为什么会这样?似乎 "tail" 进程提前终止或流混淆。
运行 另一个类似的命令按预期运行:
cat /usr/share/dict/words | tee >(grep ^a > a.txt) >(grep ^z > z.txt) > /dev/null
执行此命令后,我希望 a.txt 包含所有以 "a" 开头的单词,而 z.txt 包含所有以 "z" 开头的单词,这正是发生的事情。
那么,为什么这对 "tail" 不起作用,而对其他哪些命令不起作用?
好的,似乎发生的事情是,一旦 head -1
命令完成,它就会退出,这会导致 tee
获得一个 SIGPIPE,它会尝试写入进程替换设置的命名管道生成一个 EPIPE
并且根据 man 2 write
也会在写入过程中生成 SIGPIPE
,这会导致 tee
退出并强制 tail -1
立即退出,左边的 cat
也得到 SIGPIPE
。
如果我们使用 head
向过程中添加更多内容,并使输出更可预测并写入 stderr
而不依赖 [=17],我们可以更好地看到这一点=]:
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
this question 与 SIGPIPE
的联系方式与我们在这里看到的非常相似。
coreutils 维护者已将此作为示例添加到他们的 tee
"gotchas" 以供将来使用。
要与开发人员讨论这如何符合 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
使用 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%。
为什么会这样?似乎 "tail" 进程提前终止或流混淆。
运行 另一个类似的命令按预期运行:
cat /usr/share/dict/words | tee >(grep ^a > a.txt) >(grep ^z > z.txt) > /dev/null
执行此命令后,我希望 a.txt 包含所有以 "a" 开头的单词,而 z.txt 包含所有以 "z" 开头的单词,这正是发生的事情。
那么,为什么这对 "tail" 不起作用,而对其他哪些命令不起作用?
好的,似乎发生的事情是,一旦 head -1
命令完成,它就会退出,这会导致 tee
获得一个 SIGPIPE,它会尝试写入进程替换设置的命名管道生成一个 EPIPE
并且根据 man 2 write
也会在写入过程中生成 SIGPIPE
,这会导致 tee
退出并强制 tail -1
立即退出,左边的 cat
也得到 SIGPIPE
。
如果我们使用 head
向过程中添加更多内容,并使输出更可预测并写入 stderr
而不依赖 [=17],我们可以更好地看到这一点=]:
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
this question 与 SIGPIPE
的联系方式与我们在这里看到的非常相似。
coreutils 维护者已将此作为示例添加到他们的 tee
"gotchas" 以供将来使用。
要与开发人员讨论这如何符合 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