“grep -q”在使用“tail -f”命令时无法退出

11

我正在尝试实现一个脚本,该脚本会等待日志文件中出现特定的消息。一旦消息被记录,那么我就想继续执行脚本。

以下是我正在使用的 tail -fgrep -q

# tail -f logfile | grep -q 'Message to continue'

grep 没有退出,因此即使在文件中记录了“继续消息”,它仍会无限等待。

当我不使用 -f 运行时,似乎一切正常。


可能与尾部缓冲有关。当我运行您的命令时,它不会退出,但在再写入文件后退出。 - Kevin
6个回答

12
tail -f 会读取一个文件并显示后面添加的行,它不会终止(除非发送类似于 SIGTERM 的信号)。在这里,grep 不是阻塞的部分,tail -f 是。grep 会从管道中读取直到关闭,但它永远不会关闭,因为 tail -f 不会退出并保持管道打开。

解决您问题的方法可能是(未经测试且性能极有可能很差):

tail -f logfile | while read line; do
  echo $line | grep -q 'find me to quit' && break;
done

作为细节,中断、退出和其他信号也会停止 tail -f。尽管如此,您的诊断基本上是正确的。 - Jonathan Leffler
@Jonathan:但是我在阅读grep的manpage之后感到困惑。使用-q选项后,它应该在第一次匹配后立即退出,所以操作示例应该有效。grep会退出并停止从管道中读取。 - knittl
是的,我也感到困惑。这对我来说是一种“新”的行为;我记得 -q 相当于 -s,而早期终止令人惊讶。我最好的猜测是 grep 通过继续读取避免向 tail 发送 SIGPIPE,但我还没有证明。 (我本来要编辑我的评论以删除第二句话,但我来不及了。) - Jonathan Leffler
此外,这并没有解释为什么如果您在退出消息之后写入文件的第二行(即使只是“\n”),它也会像预期的那样退出。这对我来说意味着grep -q将按照操作者的期望方式运行,但是还存在一些奇怪的缓冲区问题。 - Kevin
作为参考,该命令的行为与 ops: tail -f xxx | perl -e 'while(<STDIN>){exit if /Stop/}' 完全相同。也就是说,它只有在第二次写入后才会正确退出。 - Kevin

4
经过一些尝试,我认为问题在于 bash 等待管道中的所有进程退出的方式存在某种问题。使用一个包含大约360行C源代码(多次连接的各种程序)的普通文件“qqq”,并使用“grep -q return”,我观察到:
  1. tail -n 300 qqq | grep -q return does exit almost at once.
  2. tail -n 300 -f qqq | grep -q return does not exit.
  3. tail -n 300 -f qqq | strace -o grep.strace -q return does not exit until interrupted. The grep.strace file ends with:

    read(0, "#else\n#define _XOPEN_SOURCE 500\n"..., 32768) = 10152
    close(1)                                = 0
    exit_group(0)                           = ?
    

    This is one leads me to think that grep has exited before the interrupt kills tail; if it was waiting for something, there would be an indication that it received a signal.

  4. A simple program that simulates what the shell does, but without the waiting, indicates that things terminate.

    #define _XOPEN_SOURCE 600
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdarg.h>
    #include <errno.h>
    #include <string.h>
    #include <stdio.h>
    
    static void err_error(const char *fmt, ...)
    {
        int errnum = errno;
        va_list args;
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        if (errnum != 0)
            fprintf(stderr, "%d: %s\n", errnum, strerror(errnum));
        exit(1);
    }
    
    int main(void)
    {
        int p[2];
        if (pipe(p) != 0)
            err_error("Failed to create pipe\n");
        pid_t pid;
        if ((pid = fork()) < 0)
            err_error("Failed to fork\n");
        else if (pid == 0)
        {
            char *tail[] = { "tail", "-f", "-n", "300", "qqq", 0 };
            dup2(p[1], 1);
            close(p[0]);
            close(p[1]);
            execvp(tail[0], tail);
            err_error("Failed to exec tail command");
        }
        else
        {
            char *grep[] = { "grep", "-q", "return", 0 };
            dup2(p[0], 0);
            close(p[0]);
            close(p[1]);
            execvp(grep[0], grep);
            err_error("Failed to exec grep command");
        }
        err_error("This can't happen!\n");
        return -1;
    }
    

    With a fixed size file, tail -f isn't going to exit - so the shell (bash) seems to hang around.

  5. tail -n 300 -f qqq | grep -q return hung around, but when I used another terminal to add another 300 lines to the file qqq, the command exited. I interpret this as happening because grep had exited, so when tail wrote the new data to the pipe, it got a SIGPIPE and exited, and bash therefore recognized that all the processes in the pipeline were dead.

我发现在使用 kshbash 时出现了相同的行为。这表明这不是一个错误,而是某种预期的行为。测试环境为Linux(RHEL 5)x86_64机器。


6
分析得很好!有趣的是,grep -q pattern <(tail -f logfile) 这个命令也能正常工作。 - Karoly Horvath
@KarolyHorvath 为什么这还不是一个答案? - o_nix

4
tail -f logfile | grep  --max-count=1  -q 'Message to continue'

诚然,它在读取下一行时才存在,而不是立即在匹配的行上。

1

我觉得我应该把这个作为答案发布,因为它解释了为什么在第二次写入文件后命令会退出:

touch xxx
tail -f xxx | grep -q 'Stop'
ps -ef |grep 'grep -q'
# the grep process is there
echo "Stop" >> xxx
ps -ef|grep 'grep -q'
# the grep process actually DID exit
printf "\n" >> xxx
# the tail process exits, probably because it receives a signal when it 
# tries to write to a closed pipe

1
实际上,当tail -f尝试在管道的读取器退出后写入新输出时,它会收到一个SIGPIPE信号。 tail -f的问题在于,通常文件的“尾部”将适合单个写入管道,即使读取器仅读取第一个字节并退出,也不会发送SIGPIPE直到尝试进行下一次写入。 - Greg A. Woods

0

这是因为使用带有-f(跟随)选项的tail不会退出,并继续向grep提供输出。使用perl/python等语言等待日志文件中的行可能更容易。

使用Python子进程模块启动tail -f。在循环中从tail读取输出,直到看到所需的行,然后退出Python脚本。将此解决方案放入您的shell脚本中。

Python脚本将阻塞shell脚本,直到看到所需的行。


0

我正在为自己的项目寻找答案。尝试测试在VMware ESXi虚拟机上何时精确地通过GPU。同样的问题有多种变化,到处都是。这个比较新。我想出了一个方法来欺骗它,如果你可以接受在日志中重复你感兴趣的行:

tail -n 1 -f /var/log/vmkernel.log | grep -m 1 IOMMUIntel >>/var/log/ vmkernel.log

这个命令会一次读取一行日志,grep检查每一行是否第一次出现,并将其附加到日志中,然后tail立即退出。

如果你喜欢VMware passthough hacking,请在这里阅读更多信息: http://hackaday.io/project/1071-the-hydra-multiheaded-virtual-computer


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接