如何通过管道从“tail -f”读取一行,然后终止?

如何通过管道从“tail -f”读取一行,然后终止?

我想通过以下方式跟踪文件的更改tail -f,然后在终止之前打印出与grep命令匹配的第一行。最简单的方法是什么?到目前为止,我一直在尝试以下方法:

tail -f foo.log | grep 'bar' | head -1

但是,管道只是挂起,大概是由于缓冲。我也试过了

tail -f foo.log | grep --line-buffered 'bar' | head -1

这会打印出该行,但除非我按下 ^C,否则进程不会终止,大概是因为需要第二行输入才能终止head -1。解决这个问题的最佳方法是什么?

答案1

tail -f foo.log | grep -m 1 bar

如果很少写入文件 foo.log,您可以执行以下操作:

grep -m 1 bar <( tail -f foo.log )

需要注意的是,tail -f 会一直停留在后台,直到得到另一行输出。如果这需要很长时间,可能会有问题。这种情况的解决办法是:

grep -m 1 bar <( exec tail -f foo.log ); kill $! 2> /dev/null

kill 将杀死剩余的 tail -f 进程,并且我们隐藏错误,因为在调用 kill 时 tail 可能已经消失了。

答案2

<() 结构仅在 bash 中有效。我的解决方案是:

sleep $timeout &
pid=$!
tail -n +0 --pid=$pid -F "$file" | ( grep -q -m 1 "$regexp" && kill -PIPE $pid )
wait $pid

除了在 POSIX shell 中工作之外,另一个优点是可以使用超时值来限制等待时间。请注意,此脚本的结果代码是反转的:0 表示已达到超时。

答案3

tail -f等待SIGPIPE,正如 depesz 指出的那样,只能tail -f在成功匹配后通过向关闭的管道再次写入 来触发,如果命令进入后台并且其后台 pid 被发送到子shell,grep -m 1则可以避免这种情况,而子shell反过来会实现在退出时明确终止。tail -fgreptraptail -f

#(tail -f foo.log & echo ${!} ) |
(tail -f foo.log & echo ${!} && wait ) | 
   (trap 'kill "$tailpid"; trap - EXIT' EXIT; tailpid="$(head -1)"; grep -m 1 bar)

答案4

我认为申请解除缓冲像这样,

tail -f foo.log | unbuffer -p grep 'bar' | head -1

会起作用。但我的系统上没有可以测试它的程序,而且我也无法解释为什么它会起作用而它却grep --line-buffered不起作用。不过,我确实尝试过这个似乎有效的替代方案:

tail -f foo.log | sed -n '/bar/{p;q}'

bar当找到匹配项时,p打印它并q立即退出。

相关内容