我想跟踪多个日志文件并将传入的行输出到单个管道。但是,不加思索地这样做(例如tail -F
)可能会产生断行:例如,两个日志中的行ABC\n
和XYZ\n
可能会混淆并变成ABXYZ\nC\n
。
以下是一个例子:
$ >a >b
$ (echo -n a >>a; sleep 2; echo A >>a) &
$ (echo -n b >>b; sleep 2; echo B >>b) &
$ tail -Fq a b
理想情况下,这会产生aA\n
和bB\n
。实际上,abA\nB\n
会产生类似的东西。
我怎样才能输出这些行而不让它们混淆?
以下是我尝试过的一些方法
我没有使用单个
tail -Fq
,而是尝试tail
为每个文件使用单独的实例:$ (trap 'kill 0' EXIT; tail -F a & tail -F b & wait)
不过,我认为这只是将问题转移
tail
到管道缓冲区,问题并没有得到解决。使用单独的实例并用于
grep
缓冲每一行。$ (trap 'kill 0' EXIT; tail -F a | grep -F '' & tail -F b | grep -F '' & wait)
这似乎有效。但是,我不确定这有多持久。我认为它具有与此问题中讨论的相同的限制:写单行时 echo 是原子的吗
(此外,有没有更好的方法来完成
grep -F ''
这里的事情?)
答案1
这是一个无需安装额外程序的解决方案,例如多尾。
它与问题的第二个示例非常相似,但要使用的命令是grep -F '' --line-buffered
。 grep -F
这里可以简称为fgrep
。关于grep -F
/fgrep
与普通正则表达式 grep,使用固定字符串 grep 是轻微地比用于相同目的的方法更快grep ^ --line-buffered
。
综合起来,多行版本如下:
(
trap 'kill 0' EXIT
tail -F a | fgrep '' --line-buffered &
tail -F b | fgrep '' --line-buffered &
wait
)
如果要将其放入 shell 脚本中,则可能不需要子 shell (
)
。要将其变成一行,请删除换行符,并在;
不以 & 符号 ( ) 结尾的行末尾添加分号 ( &
)。
深入解决方案
这实际上解决了两个问题:
首先,tail -F
它会在看到文件时使用并输出文件中的字节,而不等待行尾。这就是现状,tail
目前无法改变这一点。因此,我们无法做到这一点,tail -Fq a b
而必须为每个文件使用单独的进程。
其次,在每个文件上执行完之后tail -F
,问题仍然存在,即输出可能会在管道缓冲区中混淆。由于tail
刷新是针对任意字节而不是针对整行,因此有充分的理由出现这种情况。指定stdbuf -oL
到行缓冲区tail
不会改变这一点,因为 tail 似乎会覆盖这一点。
为了解决第二个问题,我们需要使用类似grep
等待整行后再输出的方法。此外,我们需要指定,--line-buffered
否则 grep 本身将缓冲其输出并在缓冲区已满时刷新,这可能不在行边界。
各种各样的
为了解释trap 'kill 0' EXIT
...的作用,当使用类似-之类的命令来中断时wait
,需要防止tail -F
进程被抛在后面。CtrlC