我可能误解了这里发生的事情,但我觉得这与管道缓冲有关。我有一个脚本,它使用多个文件描述符(#3 及以上)来实现各种日志记录级别。根据命令行选项,其中几个可能会转到同一个文件,一些到控制台,一些到/dev/null
.我总是将其中一个(例如标准输出)发送到文件,当我需要向该文件发送更多内容时,我将它们重定向到标准输出,而不是文件。这是因为我注意到,当我将多个文件描述符重定向到一个文件时,它们到达时是无序的(这是有道理的)。也就是说我愿意
exec >/some/file 3>&1
而不是
exec >/some/file 3>/some/file
到目前为止,一切都很好。然而,有时我需要获取命令的错误输出并将其发送到我的自定义描述符之一,例如 3,它可能会发送到 stdout(而 stdout 又会发送到文件)。然后我收到的消息乱序。来自该命令的消息出现在来自后续命令的消息之后。这是一个小型 PoC。我究竟做错了什么?
#!/bin/bash
check_if_ordered() {
sort -n -k1 -k2 test_out.txt > test_out_sorted.txt
if ! diff test_out.txt test_out_sorted.txt >/dev/null ; then
echo "Oops, messages are NOT in order" >&2
else
echo "Good, messages are in order" >&2
fi
rm test_out.txt test_out_sorted.txt
}
log() {
while read msg; do
echo "$msg"
done
}
foo() {
for i in {1..150} ; do
echo "$1 $i"
done
}
#### This always works OK, but can't use it in my scenario
echo "Redirecting command output to file"
foo "1" > >(log) > test_out.txt
foo "2" > >(log) >> test_out.txt
check_if_ordered
#### This is similar to what I need to do and always fails
echo "Redirecting stdout to file"
exec >test_out.txt
foo "1" > >(log)
foo "2" > >(log)
check_if_ordered
我应该补充一点,我知道可以禁用命令缓冲的外部工具,但在这种情况下我不能使用这些工具(我的脚本需要尽可能可移植并在各种发行版上运行)。
答案1
#### This always works OK, but can't use it in my scenario
foo "1" > >(log) > test_out.txt
foo "2" > >(log) >> test_out.txt
这里的进程替换似乎是多余的,因为下一个重定向会覆盖 stdout 直接到文件的重定向。也就是说,第一个应该相当于foo "1" > test_out.txt
。
#### This is similar to what I need to do and always fails
exec >test_out.txt
foo "1" > >(log)
foo "2" > >(log)
在这里,我认为问题在于进程替换是异步运行的,并且由于循环while read; do echo
很慢,因此当第二个启动时,第一个log
仍在运行并从管道缓冲区中读取。它类似于echo foo > >(cat; sleep 1; echo hi)
在命令行上执行类似操作,hi
在显示下一个提示后就会出现。另外,wait
这里似乎也没有帮助。
但我不确定为什么你log
首先需要两份副本?不是只有一个人会这样做:
exec >test_out.txt
exec 9> >(log)
foo "1" >&9
foo "2" >&9
(在我的系统上,使用cat
而不是while read; do echo
至少也隐藏了问题,因为cat
速度更快并且读取更大的块。但这并不意味着它会完全修复它,并且您可能想做一些除了相同副本之外的事情在log
。)
我应该补充一点,我知道可以禁用命令缓冲的外部工具
如果你的意思是类似的东西stdbuf -o0
,那只会有助于进程内部的缓冲(在C库中),我认为这在这里不起作用。 shellecho
实际上应该在返回之前将数据写入操作系统。