【问题标题】:Do a tail -F until matching a pattern执行 tail -F 直到匹配模式
【发布时间】:2011-06-28 19:17:09
【问题描述】:

我想对文件执行tail -F 直到匹配模式。我找到了一种使用awk 的方法,但恕我直言,我的命令并不是很干净。问题是我需要只用一行来完成,因为有一些限制。

tail -n +0 -F /tmp/foo | \
awk -W interactive '{if ($1 == "EOF") exit; print} END {system("echo EOF >> /tmp/foo")}'

EOF 出现在文件中之前,尾部会一直阻塞。它工作得很好。 END 块是强制性的,因为 awk 的 exit 不会立即退出。它使awkeval 在退出之前成为END 块。 END 块挂在读取调用上(因为 tail),所以我需要做的最后一件事是在文件中写另一行以强制 tail 退出。

有人知道更好的方法吗?

【问题讨论】:

标签: shell sed awk tcl tail


【解决方案1】:

使用tail 的--pid 选项,当shell 死亡时tail 将停止。无需向 tailed 文件添加额外内容。

sh -c 'tail -n +0 --pid=$$ -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

【讨论】:

  • 不错的改进!这应该是公认的答案。
  • 有什么方法可以摆脱至少在我从脚本中使用时出现的7616 Terminated sh -c ... 行?
  • 如果你的tail不支持--pid选项,你可以使用sh -i -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill 0 ;}'sh -i 创建一个新的进程组,kill 0 杀死当前进程组中的所有进程。
  • 注意:这在 BusyBox 中不起作用,因此在 Alpine 中也不起作用。
  • @sampo 你可以grep -v "Terminated sh -c" 它。它打印所有不匹配的行。
【解决方案2】:

试试这个:

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

只要在/tmp/foo 中看到“EOF”字符串,整个命令行就会退出。

有一个副作用:tail 进程将一直运行(在后台),直到有任何内容写入/tmp/foo

【讨论】:

  • 只是解释一下:“sh -c”用于将管道运行到子shell中,并且能够检索子shell PID。行尾的“$$”将扩展为该子shell 的PID。我的 sed 脚本应该和你的 awk 脚本做同样的事情(即显示它看到的所有内容,并在遇到字符串“EOF”时退出)。一旦 sed 找到“EOF”字符串,“kill”将终止子 shell。 “尾部”悬空进程将保留,因为它将在 /tmp/foo 上循环。一旦将某些内容写入文件,tail 就会回显它,并且由于 {...} 部分已退出而终止。
  • 查看@GregBarrett 的答案,了解悬垂尾进程的修复方法。
【解决方案3】:

我的解决方案没有结果:

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

存在一些与缓冲区相关的问题,因为如果没有更多的行附加到文件中,那么 sed 将不会读取输入。所以,通过更多的研究,我想出了这个:

sed '/EOF/q' <(tail -n 0 -f /tmp/foo)

脚本在https://gist.github.com/2377029

【讨论】:

    【解决方案4】:

    这是 Tcl 非常擅长的。如果下面是“tail_until.tcl”,

    #!/usr/bin/env tclsh
    
    proc main {filename pattern} {
        set pipe [open "| tail -n +0 -F $filename"]
        set pid [pid $pipe]
        fileevent $pipe readable [list handler $pipe $pattern]
        vwait ::until_found
        catch {exec kill $pid}
    }
    
    proc handler {pipe pattern} {
        if {[gets $pipe line] == -1} {
            if {[eof $pipe]} {
                set ::until_found 1
            }
        } else {
            puts $line
            if {[string first $pattern $line] != -1} {
                set ::until_found 1
            }
        }
    }
    
    main {*}$argv
    

    那么你会做tail_until.tcl /tmp/foo EOF

    【讨论】:

    • 很不错,但我必须把它当作单线。
    • @shad:没有额外文件的单线? :-(
    【解决方案5】:

    这对你有用吗?

    tail -n +0 -F /tmp/foo | sed '/EOF/q'
    

    我假设“EOF”是您正在寻找的模式。 sed 命令在找到时退出,这意味着tail 应该在下次写入时退出。

    我认为,如果在文件末尾附近找到该模式,tail 有可能会在外面徘徊,等待更多输出出现在文件中,而这些输出永远不会出现。如果这确实是一个问题,您可能会安排将其终止 - 当sed 终止时,整个管道将终止(除非您使用的是一个有趣的外壳,它认为这不是正确的行为)。


    抱怨 Bash

    正如人们担心的那样,bash(至少在 MacOS X 上,但可能无处不在)是一个 shell,它认为它需要等待tail 完成,即使sed 退出。有时——比我喜欢的更频繁——我更喜欢旧的 Bourne shell 的行为,它不那么聪明,因此比 Bash 猜错的次数更少。 dribbler 是一个每秒发出一条消息的程序(示例中为“1:Hello”等),输出到标准输出。在 Bash 中,此命令序列一直挂起,直到我在单独的窗口中执行了“echo pqr &gt;&gt;/tmp/foo”。

    date
    { timeout -t 2m dribbler -t -m Hello; echo EOF; } >/tmp/foo &
    echo Hi
    sleep 1   # Ensure /tmp/foo is created
    tail -n +0 -F /tmp/foo | sed '/EOF/q'
    date
    

    遗憾的是,我没有立即看到控制这种行为的选项。我确实找到了shopt lithist,但这与这个问题无关。

    Korn Shell 万岁

    我注意到,当我使用 Korn shell 运行该脚本时,它会按我的预期工作 - 留下一个 tail 潜伏在附近以某种方式被杀死。在第二个日期命令完成后,'echo pqr &gt;&gt; /tmp/foo' 有效。

    【讨论】:

    • 我真的很喜欢你的详细解释。您的第一个命令可能是最好的,除了当我没有指定 END 块时 sed 的行为与 awk 相同。所以我想我会暂时保留我的 awk 命令。
    【解决方案6】:

    这是 Jon 解决方案的扩展版本,它使用 sed 而不是 grep,以便 tail 的输出进入标准输出:

    sed -r '/EOF/q' <( exec tail -n +0 -f /tmp/foo ); kill $! 2> /dev/null
    

    这是有效的,因为 sed 在 tail 所以 $!持有tail的PID

    与 sh -c 解决方案相比,它的主要优点是杀死 sh 似乎会在输出中打印一些内容,例如不受欢迎的 'Terminated'

    【讨论】:

    • 我在 ksh 中没有任何运气。 kill $! 实际上杀死了最后一个后台作业,而不是 tail 命令。
    【解决方案7】:
    sh -c 'tail -n +0 --pid=$$ -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'
    

    这里的主要问题是$$。 如果您按原样运行命令,$$ 不会设置为 sh,而是设置为运行命令的当前 shell 的 PID。

    要使 kill 起作用,您需要将 kill $$ 更改为 kill \$$

    之后,您可以安全地摆脱传递给 tail 命令的--pid=$$

    总结一下,以下就可以了:

    /bin/sh -c 'tail -n 0 -f /tmp/foo | { sed "/EOF/ q" && kill \$$ ;}
    

    您可以选择将-n 传递给sed 以保持安静:)

    【讨论】:

      【解决方案8】:

      要杀死悬空的tail 进程,您可以在(Bash)进程替换上下文中执行tail 命令,以后可以将其杀死,就好像它是后台进程一样。 (代码取自How to read one line from 'tail -f' through a pipeline, and then terminate?)。

      : > /tmp/foo
      grep -m 1 EOF <( exec tail -f /tmp/foo ); kill $! 2> /dev/null
      echo EOF > /tmp/foo  # terminal window 2
      

      作为替代方案,您可以使用命名管道。

      (
      : > /tmp/foo
      rm -f pidfifo
      mkfifo pidfifo
      sh -c '(tail -n +0 -f /tmp/foo & echo $! > pidfifo) | 
      { sed "/EOF/ q" && kill $(cat pidfifo) && kill $$ ;}'
      )
      
      echo EOF > /tmp/foo  # terminal window 2
      

      【讨论】:

        【解决方案9】:

        准备用于tomcat =

        sh -c 'tail -f --pid=$$ catalina.out | { grep -i -m 1 "服务器启动在" && kill $$ ;}'

        对于上述场景:

        sh -c 'tail -f   --pid=$$ /tmp/foo | { grep -i -m 1 EOF && kill $$ ;}'
        

        【讨论】:

        • 这正是我想要的(检查 tomcat 启动),谢谢!
        【解决方案10】:
        tail -f <filename> | grep -q "<pattern>"
        

        【讨论】:

        • 在模式之前什么都不打印,然后不退出尾部。失败。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-20
        • 1970-01-01
        • 2015-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多