如何在连续的数据流中使用grep命令?

870

是否可以在连续流上使用 grep 呢?

我的意思是类似于 tail -f <file> 命令,但是将 grep 应用于输出结果,以便只保留我感兴趣的行。

我尝试过 tail -f <file> | grep pattern,但似乎 grep 只能在 tail 结束后执行,也就是说永远不会执行。


13
生成该文件的程序很可能没有刷新其输出。 - Steve-o
1
tail -f file 可以工作(我可以实时看到新的输出)。 - Matthieu Napoli
8
这个网址是unix.stackexchange.com,翻译成中文是“Unix技术交流论坛”,是否合适取决于您的需求。 - Luc M
这是一个常见问题解答:http://mywiki.wooledge.org/BashFAQ/009 - tripleee
呵呵,在管道中缓冲!也许这些答案对于在此搜索此主题的某些人会有所帮助。这些答案 - xealits
显示剩余4条评论
13个回答

1529

在使用BSD grep(FreeBSD,Mac OS X 等)时,打开 grep 的行缓冲模式。

tail -f file | grep --line-buffered my_pattern

看起来之前在GNU grep(在几乎所有Linux上使用)中,--line-buffered并不重要,因为它默认会刷新(对于其他类Unix系统如SmartOS、AIX或QNX可能有所不同)。然而,从2020年11月起,--line-buffered是必需的(至少在openSUSE中使用GNU grep 3.5时是这样的,但根据下面的评论似乎通常需要这样做)。


4
你可以使用命令 "tail -F file | grep --line-buffered my_pattern",意思是实时监控文件内容并在其中搜索符合 "my_pattern" 的行。 - jcfrei
60
@MichaelGoldshteyn 别着急。人们之所以会点赞,是因为当他们谷歌搜索“grep line buffered”时,找到了这个页面,并且它解决了他们的问题,尽管可能与提出的问题不完全一样。 - raine
4
我来这里是为了查找 strace 的输出。如果没有使用 --line-buffered,它将无法正常工作。 - sjas
7
@MichaelGoldshteyn(及其评论中的点赞者):我一直对“tail -f | grep”存在问题,而“--line-buffered”对我有所帮助(在Ubuntu 14.04上,GNU grep版本为2.16)。 “如果标准输出是tty,则使用行缓冲”的逻辑实现在哪里?在http://git.savannah.gnu.org/cgit/grep.git/tree/src/grep.c中,“line_buffered”仅由参数解析器设置。 - Aasmund Eldhuset
8
我使用的是BSD grep在macOS系统上,如果不加上--line-buffered参数就得不到输出。不过经过测试,发现GNU grep会按照您所描述的方式运行。因此,与Unix的大多数事情一样,它取决于您所使用的平台实现。由于问题没有指定平台,因此提供的信息似乎是错误的 - 经过对比BSD grep和GNU grep的代码后,行为显然受--line-buffered选项控制。只是默认情况下只有GNU grep会刷新输出。 - Richard Waite
显示剩余5条评论

134

我经常使用 tail -f <文件名> | grep <匹配模式> 命令。

它会等待 grep 刷新,而不是等待它执行完(我使用的是 Ubuntu 操作系统)。


8
持续时间可能会相当长,所以尽量不要心急。 - glglgl
大约需要多长时间? - Matthieu Napoli
2
@Matthieu:主要取决于您在grep中搜索什么以及您的操作系统缓冲区有多大。如果grep只匹配短字符串,每隔几个小时才会出现第一次刷新,那么可能需要数天时间。 - tripleee
16
Tail 不使用输出缓冲,grep 使用。 - XzKto
7
不,当输出流向tty设备时(如此答案中),grep不会进行输出缓冲,它会进行行缓冲!这是正确答案,应该被接受。有关更多详细信息,请参阅我对当前已接受(错误)答案的较长评论。 - Michael Goldshteyn
显示剩余2条评论

85

我认为你的问题是grep使用了一些输出缓冲。尝试

tail -f file | stdbuf -o0 grep my_pattern

它将把grep的输出缓冲模式设置为无缓冲。


9
这种方法的好处是除了 grep 命令之外,还可以用于许多其他命令。 - Peter V. Mørch
4
然而,经过更多的尝试后,我发现某些命令仅在连接到 tty 时才刷新其输出,对于这种情况,“unbuffer”(在 Debian 的“expect-dev”包中)非常有效。因此,我会选择使用 unbuffer 而不是 stdbuf。 - Peter V. Mørch
7
@Peter V. Mørch 是的,你说得对,unbuffer有时候可以解决stdbuf无法解决的问题。但我认为你在寻找一个“神奇”的程序,总是希望它能够解决你的所有问题,而不是理解你的问题。创建虚拟tty是一个无关的任务。Stdbuf正好符合我们的要求(设置标准输出缓冲区的值),而unbuffer会做很多隐藏的事情,这些事情可能并不是我们想要的(比较交互式的 top 命令使用stdbuf和unbuffer命令)。并且真的没有“神奇”的解决方案:unbuffer 有时也会失败,例如 awk 使用不同的缓冲实现(stdbuf 也会失败)。 - XzKto
2
但我认为你正在尝试寻找一个“神奇”的程序,它总是可以解决你的问题,而不是理解你的问题。 - 我想你是对的!;-) - Peter V. Mørch
1
关于stdbufunbuffer和stdio缓冲的更多信息,请访问http://www.pixelbeat.org/programming/stdio_buffering/。 - Tor Klingberg
@PeterV.Mørch "但我认为你正在试图寻找一种'魔法'...难道我们不都是吗;-?祝大家好运。" - shellter

20

如果您想在整个文件中查找匹配项(而不仅仅是尾部),并且希望它等待任何新匹配项,那么这个方法很好用:

tail -c +0 -f <file> | grep --line-buffered <pattern>

使用-c +0选项,表示输出应该从文件的开头(+)开始0字节(-c)。


17
在大多数情况下,您可以使用命令tail -f /var/log/some.log | grep foo并且它将正常工作。
如果您需要在运行的日志文件上使用多个grep,并发现您没有输出,则可能需要将--line-buffered开关插入到中间grep中,就像这样:
tail -f /var/log/some.log | grep --line-buffered foo | grep bar

12
你可以把这个答案看作是一个增强版...通常我使用的是。
tail -F <fileName> | grep --line-buffered  <pattern> -A 3 -B 5

-f在文件轮转时效果不好,而-F在这种情况下更好。

-A和-B可以用来获取模式匹配前后的行,这些块将出现在虚线分隔符之间。

但对我来说,我更喜欢按照以下方式操作。

tail -F <file> | less

如果您想在流日志中搜索,这将非常有用。我是说可以向前和向后查看并深入查看。


6
如果N相同,grep -C 3 <pattern> 取代了 -A <N>-B <N> 的功能。这个命令会显示匹配 <pattern> 的行,并显示前后三行。 - AKS

7

我平常处理这种情况时,没有看到有人提供我的首选方法:

less +F <file>
ctrl + c
/<search term>
<enter>
shift + f

我更喜欢这种方式,因为你可以使用 ctrl + c 在任何时候停止和导航文件,然后只需按下 shift + f 返回到实时流搜索。


4
有点晚回答这个问题了,考虑到这种工作是监控工作的重要部分,以下是我的(不太短的)回答...

使用跟踪日志

1. 命令tail

这个命令比已经发布过的答案更强大。

  1. Difference between follow option tail -f and tail -F, from manpage:

       -f, --follow[={name|descriptor}]
              output appended data as the file grows;
    ...
       -F     same as --follow=name --retry
    ...
       --retry
              keep trying to open a file if it is inaccessible
    

    This mean: by using -F instead of -f, tail will re-open file(s) when removed (on log rotation, for sample).
    This is usefull for watching logfile over many days.

  2. Ability of following more than one file simultaneously
    I've already used:

    tail -F /var/www/clients/client*/web*/log/{error,access}.log /var/log/{mail,auth}.log \
               /var/log/apache2/{,ssl_,other_vhosts_}access.log \
               /var/log/pure-ftpd/transfer.log
    

    For following events through hundreds of files... (consider rest of this answer to understand how to make it readable... ;)

  3. Using switches -n (Don't use -c for line buffering!).
    By default tail will show 10 last lines. This can be tunned:

    tail -n 0 -F file
    

    Will follow file, but only new lines will be printed

    tail -n +0 -F file
    

    Will print whole file before following his progression.

2. 管道传输时的缓冲问题:

如果您计划过滤输出,请考虑缓冲!请参阅sed-u选项,grep--line-buffered选项或stdbuf命令:

tail -F /some/files | sed -une '/Regular Expression/p'

使用“sed”命令时,如果不使用“-u”开关,则使用“(比使用grep更有效)”比不使用更加反应灵敏。
tail -F /some/files |
    sed -une '/Regular Expression/p' |
    stdbuf -i0 -o0 tee /some/resultfile

3. 最近的日志记录系统

在最近的系统中,您需要运行journalctl -xf而不是tail -f /var/log/syslog,方式几乎相同...

journalctl -axf | sed -une '/Regular Expression/p'

但是请阅读man页面,这个工具是为日志分析而建立的!
4. 将其集成到脚本中。
  1. Colored output of two files (or more)

    Here is a sample of script watching for many files, coloring ouptut differently for 1st file than others:

    #!/bin/bash
    
    tail -F "$@" |
        sed -une "
            /^==> /{h;};
            //!{
                G;
                s/^\\(.*\\)\\n==>.*${1//\//\\\/}.*<==/\\o33[47m\\1\\o33[0m/;
                s/^\\(.*\\)\\n==> .* <==/\\o33[47;31m\\1\\o33[0m/;
                p;}"
    

    They work fine on my host, running:

    sudo ./myColoredTail /var/log/{kern.,sys}log
    
  2. Interactive script

    You may be watching logs for reacting on events?

    Here is a little script playing some sound when some USB device appear or disappear, but same script could send mail, or any other interaction, like powering on coffe machine...

    #!/bin/bash
    
    exec {tailF}< <(tail -F /var/log/kern.log)
    tailPid=$!
    
    while :;do
        read -rsn 1 -t .3 keyboard
        [ "${keyboard,}" = "q" ] && break
        if read -ru $tailF -t 0 _ ;then
            read -ru $tailF line
            case $line in
                *New\ USB\ device\ found* ) play /some/sound.ogg ;;
                *USB\ disconnect* ) play /some/othersound.ogg ;;
            esac
            printf "\r%s\e[K" "$line"
        fi
    done
    
    echo
    exec {tailF}<&-
    kill $tailPid
    

    You could quit by pressing Q key.


1
优秀而详尽的回答。谢谢。 - Dudi Boy

4

sed是一个更好的选择(流编辑器

tail -n0 -f <file> | sed -n '/搜索字符串/p'

如果您想要在找到特定字符串后退出tail命令:

tail --pid=$(($BASHPID+1)) -n0 -f <file> | sed -n '/搜索字符串/{p; q}'

显然这是一个bashism: $BASHPID将是tail命令的进程id。 sed命令紧随tail在管道中,所以sed的进程id将是$BASHPID+1。


1
在许多情况下,假设系统上启动的下一个进程($BASHPID+1)将是您自己的进程是错误的,并且这对于解决缓冲问题毫无作用,这可能是OP想要询问的问题。特别是,在这里推荐使用sed而不是grep似乎只是一种(可疑的)个人喜好。(如果您试图传达的是p;q行为,则可以通过grep -m 1实现。) - tripleee
工作正常,sed命令会在每行准备好时立即打印出来,而带有“--line-buffered”选项的grep命令则不会。我真诚地不理解减1是什么意思。 - MUY Belgium
已经证实,缓冲是grep的问题。使用sed处理行缓冲不需要特殊操作,这是默认行为,因此我强调了“流”这个词。确实,不能保证$BASHPID+1是要跟随的正确pid,但由于pid分配顺序且管道命令紧随其后立即被分配一个pid,这是非常可能的。 - Christian Herr

2

是的,这实际上完全可行。 Grep 和大多数 Unix 命令逐行处理流。每一行从 tail 中输出的行都将被分析并在匹配时传递。


3
如果grep是管道链中的最后一条命令,那么它会按照你所说的那样执行。然而,如果它在中间位置,它会每次缓冲大约8k的输出。 - Mahmoud Al-Qudsi

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