【问题标题】:Ending tail -f started in a shell script结束 tail -f 在 shell 脚本中开始
【发布时间】:2011-01-03 17:28:57
【问题描述】:

我有以下。

  1. 将日志写入标准输出的 Java 进程
  2. 一个启动 Java 进程的 shell 脚本
  3. 另一个 shell 脚本执行前一个并重定向日志
  4. 我使用tail -f 命令检查日志文件以获取成功消息。

即使我在代码中有 exit 0,我也无法结束 tail -f 进程。

这不会让我的脚本完成。在 Bash 中有没有其他方法可以做到这一点?

代码如下所示。

function startServer() {
  touch logfile
  startJavaprocess > logfile &

  tail -f logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done
}

【问题讨论】:

  • 不是一个很好的答案,但是在脚本结束后,尾巴会在第一个输出处死在损坏的管道上。
  • 问题是如果java进程启动后不写任何东西会发生什么,似乎尾巴一直在等待。
  • 它是否曾经回显“服务器已启动”?
  • 不过也要小心,在启动服务器之前启动尾部(使用 -n0 以不读取任何旧条目)。否则它可能已经超出了给你的“回滚”尾巴(我相信默认是五行)
  • @Alex Brown,是的!它回显“服务器已启动”。并且循环也结束了。我验证过了。

标签: bash tail


【解决方案1】:

我能想到的最佳答案是这个

  1. 设置读取超时,tail -f logfile | read -t 30 line
  2. 以--pid=$$ 开头,这样它会在 bash 进程完成后退出。

它将涵盖我能想到的所有情况(服务器挂起没有输出、服务器退出、服务器正常启动)。

别忘了在服务器之前开始你的尾巴。

tail -n0 -F logfile 2>/dev/null | while read -t 30 line

-F 将“读取”文件,即使它不存在(当它出现时开始读取)。 -n0 不会读取文件中已有的任何内容,因此您可以继续附加到日志文件而不是每次都覆盖它,并对其进行标准日志轮换。

编辑:
好的,如果您使用的是tail,那么这是一个相当粗略的“解决方案”。除了tail之外,可能有更好的解决方案,但我必须把它给你,tail可以很好地让你摆脱破损的管道。能够处理 SIGPIPE 的“三通”可能会更好。使用某种“我还活着”消息主动执行文件系统删除的 java 进程可能更容易等待。

function startServer() {
  touch logfile

  # 30 second timeout.
  sleep 30 &
  timerPid=$!

  tail -n0 -F --pid=$timerPid logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      # stop the timer..
      kill $timerPid
    fi
  done &

  startJavaprocess > logfile &

  # wait for the timer to expire (or be killed)
  wait %sleep
}

【讨论】:

  • 没用,$$ 是一直在运行的 bash -hB 进程
  • 我明白了,我认为您正在运行一个脚本来启动您的服务器,而不是您让一个脚本同时启动和停止。但无论如何,这应该是可以解决的......我会考虑一下并回复你
  • 我目前不在系统上,但请尽快尝试并通知您。
  • 谢谢。这有效,但前提是我在尾部和循环之前启动脚本。否则我只会得到超时没有grep。另一个缺点是我从 bash 中获得了 kill 和 if wait 的输出,因为 sleep 已经结束了。
  • 到目前为止,这是最适合我的答案。
【解决方案2】:

根据我在这里找到的答案,这就是我想出的。

它直接处理 tail 并在我们看到所需的日志输出后将其杀死。使用 'pkill -P $$ tail' 应该确保正确的进程被杀死。

wait_until_started() {
    echo Waiting until server is started
    regex='Started'
    tail logfile -n0 -F | while read line; do
            if [[ $line =~ $regex ]]; then
                    pkill -9 -P $$ tail
            fi
    done
    echo Server is started
}

【讨论】:

  • 这是一颗宝石!真的在互联网上到处找这个。我相信这应该是公认的答案:)
  • 更新:实际上,虽然这在命令行中可以正常工作,但在通过 SSH 运行时它就不能正常工作了。我所做的是将 pkill -9 -P -$$ tail 替换为 killall tail。有点粗略,但可以。
【解决方案3】:

根据tail man page,可以在a进程死亡后让tail终止

在 BASH 中,您可以使用 $! 获取上次启动的后台进程的 PID!所以如果你使用 bash:

tail -f --pid=$! logfile

【讨论】:

  • 问题是脚本正在等待。我对关闭脚本执行相同的技巧,它可以工作,因为关闭脚本完成了。但是启动脚本挂了。
  • 这不会停止尾部直到服务器停止,这可能是几年后。改用 $$,它会在脚本退出时触发。
  • @roe,你能详细说明一下吗?我的问题是服务器和脚本都没有终止
【解决方案4】:

我遇到过类似的情况,我需要在合理的时间内跟踪日志以获取“已启动”消息,如果在此期间未找到,我需要退出。这就是我最终要做的。

wait_tomcat_start(){
WAIT=60
echo "Waiting for Tomcat to initialize for $WAIT seconds"

# Tail log file, do a while read loop with a timeout that checks for desired log status,
# if found kill the find and break the loop. If not found within timeout: the read -t will
# kill the while read loop and bounce to the OR statement that will in turn kill the tail 
# and echo some message to the console.
tail -n0 -f $SERVERLOG | while read -t $WAIT LINE || (pkill -f "tail -n0 -f" && echo "Tomcat did not start in a timely fashion! Please check status of tomcat!!!")
do
        echo "$LINE"
        [[ "${LINE}" == *"Server startup in"* ]] && pkill -f "tail -n0 -f" && break
done
}

我不确定这是否非常优雅,甚至不是最好的方法,但它对我来说已经足够好了。我很高兴有任何意见:)

【讨论】:

    【解决方案5】:

    捕获后台进程的pid

    pid=$!
    

    使用 tail 的 --pid=PID 选项,使其在具有 pid $PID 的进程终止后终止。

    【讨论】:

      【解决方案6】:

      您可以找到 tail -f 进程的进程 ID 并将其杀死(如果您确定日志文件已完成,kill -9 在这里甚至是安全的),而不是退出进程。

      这样,while read line 将自然终止,您无需退出。

      或者,由于您并没有真正使用tail 来输出到屏幕,您也可以尝试更老式的:

      grep -q 'Started' logfile
      while [[ $? -ne 0 ]] ; do
          sleep 1
          grep -q 'Started' logfile
      done
      

      【讨论】:

      • 那里可能有早期课程的“开始”,所以我不会打赌你的老派版本
      • 不,不会有,因为java进程正在覆盖日志文件,而不是附加到它。
      • 对,我想我没注意。清洁工应该是while ! grep -q 'Started' logfile; do sleep 1; done
      【解决方案7】:

      使用无限循环而不是tail的-f命令行选项怎么样?

      function startServer() {
        startJavaprocess > logfile &
      
        while [ 1 ]
        do
         if tail logfile | grep -q 'Started'; then
          echo 'Server started'
          exit 0
         fi
        done
      }
      

      【讨论】:

      • 这还不错,但有可能会错过“开始”行,例如,如果它写在循环之间,后面跟着足够多的行以阻止它出现在下一个尾部。
      • 如果你想完全消除风险,你可以在整个文件中使用 grep。
      【解决方案8】:

      我有同样的问题,找不到简单而好的解决方案。我不擅长 Python,但我设法解决了这个问题:

      wait_log.py:

      #!/usr/bin/env python
      
      from optparse import OptionParser
      import os
      import subprocess
      import time
      
      def follow(file):
          def file_size(file):
              return os.fstat(file.fileno())[6]
          def is_newLine(line):
              return line != None and line.find("\n") != -1;
      
          file.seek(0, os.SEEK_END)
      
          while True:
              if file.tell() > file_size(file):
                  file.seek(0, os.SEEK_END)
      
              line_start = file.tell()
              line = file.readline()
      
              if is_newLine(line):
                  yield line
              else:
                  time.sleep(0.5)
                  file.seek(line_start)
      
      def wait(file_path, message):
          with open(file_path) as file:
              for line in follow(file):
                  if line.find(message) != -1:
                      break
      
      def main():
          parser = OptionParser(description="Wait for a specific message in log file.", usage="%prog [options] message")
          parser.add_option("-f", "--file", help="log file")
      
          (options, args) = parser.parse_args()
      
          if len(args) != 1:
              parser.error("message not provided")
      
          if options.file == None:
              parser.error("file not provided")
      
          wait(options.file, args[0])
      
      if __name__ == "__main__":
          main()
      

      【讨论】:

        【解决方案9】:

        有一个类似的问题,当

        时尾部进程没有被杀死
        1. 通过jsch运行
        2. tail 没有向 jsch 产生任何输出,因此也没有向其输出流产生任何输出。

        使用--pid=$! 将其杀死并启动无限while 循环以在后台回显尾部之前的某些内容,当底层进程被杀死并因此杀死尾部时该尾部被杀死。

        ( while true; do echo 'running';  sleep 5; done ) & ( tail -f --pid=$! log-file )
        

        【讨论】:

          【解决方案10】:

          我对这个问题的首选解决方案是将“tail”命令及其使用者放入一个子shell,并让过滤逻辑杀死父进程及其子进程(包括尾进程)。如果您查看进程树,它将是:

          startServer (pid=101)
             startServer (pid=102) << This is the subshell created by using parens "(...)"
                tail -f logfile (pid=103) << Here's the tail process
                startServer (pid=104)     << Here's the logic that detects the end-marker
          

          在这种方法中,结束标记检测逻辑 (pid 104) 查找其父 PID (102) 及其所有子代,并杀死整个批次,包括它自己。然后祖父母(上面的pid 101)可以继续。

          function startServer() {
            touch logfile
            startJavaprocess > logfile &
          
            tail -f logfile | while read line 
            do
              if echo $line | grep -q 'Started'; then
                echo 'Server Started'
                mypid=$BASHPID
                pipeParent=$(awk '/^PPid/ {print $2}' /proc/$mypid/status)
                kill -TERM $pipeParent $(pgrep -P $pipeParent)  # Kill the subshell and kids
              fi
            done
          }
          
          # To invoke startServer(), add a set of parens -- that puts it in a subshell:
          (startServer())
          

          【讨论】:

            【解决方案11】:

            可以后台tail -f logfile,将tailpid 发送到while read 循环子shell 并在EXIT 上执行trap 以终止tail 命令。

            ( (sleep 1; exec tail -f logfile) & echo $! ; wait) | (
              trap 'trap - EXIT; kill "$tailpid"; exit' EXIT
              tailpid="$(head -1)"
              while read line 
              do
                if echo $line | grep -q 'Started'; then
                  echo 'Server Started'
                  exit 0
                fi
              done
            )
            

            【讨论】:

              【解决方案12】:

              这应该可以工作,并且一旦子外壳死亡,尾巴应该死亡

              
              function startServer() {
                touch logfile
                startJavaprocess &gt logfile &
              
                while read line 
                do
                  if echo $line | grep -q 'Started'; then
                    echo 'Server Started'
                    exit 0
                  fi
                done &lt &lt(tail -f logfile)
              }
              

              试试这个:

              功能启动服务器(){ 而读线 做 如果回显 $line | grep -q '开始';然后 echo '服务器已启动' 返回 0 菲 完成 &lt &lt(startJavaprocess | tee 日志文件) }

              【讨论】:

                【解决方案13】:

                不要使用 tail - 您可以使用 read 获得相同的“监视文件中的最新内容”。

                这里我使用FIFO 代替日志文件:

                function startServer() {
                  mkfifo logfile
                  startJavaprocess > logfile &
                
                  a=""; while [ "$a" != "Started" ]; do read <logfile a; done
                
                  echo "Server Started"
                }
                

                请注意,这会留下一个 FIFO。

                【讨论】:

                • 这将在删除 fifo 时杀死 java 进程(损坏的管道),或者在第二个示例中,当循环退出时。可能不是你想要的。
                • 是的,我在第二个实例中发现了终止,但不是第一个。第一个简单的修复,我会删除第二个,直到我想办法解决这个问题。
                【解决方案14】:

                使用 tail -n0 -f piped 到 grep 确实是一个不错的解决方案,实际上,当管道中的第一个进程尝试输出到一个死的 grep 进程时,它会死掉。

                但是,如果您正在寻找出现在尾部当前输出附近的文本,那么 grep 已经从尾部读取了整个输入(在一个块中),因此不会再有任何文本日志中需要向下发送管道的输出,因为 grep 在退出之前已经读取了它(或者它可能已经在管道缓冲区中) - 至少这是我的理解。

                在 grep 上使用“-m1”选项看起来完全符合您的要求,并在匹配的行之后立即保留输入,但它似乎没有任何区别或帮助我搜索类似功能。我怀疑管道缓冲区仍然保存从尾部输出的所有文本,或者是尾部没有任何输出的其他原因。您希望此 grep-match 后文本仍留在下一个输出,因为它会在尝试时杀死您的尾巴(仍然有风险 - 如果出于某种原因它的最后一行会发生什么?),并将控制权返回给调用脚本.

                我发现一种方法是在 grep 退出后将任何内容输出到日志文件的末尾;即。

                tail -f 日志文件 | ( grep -q ; echo >> 日志文件)

                我有一个理论(如果我的猜测是正确的)你可以强制管道减少缓冲以使其在没有这个的情况下工作,或者可能将 huponexit 设置命令添加到适当的管道组件 - 即在(可能是卷曲的) 括号会有所帮助;但我并不关心在日志文件中添加一个空行,它工作正常,而且它只是一个较小的测试脚本(所以不是一个需要坚持其他处理格式的长期日志文件)。

                shopt -s huponexit 会很有用,但它的子外壳性。

                PS 我在这里的第一篇文章,希望将其作为对现有答案的评论而不是重复内容,但我认为我现在不能。

                【讨论】:

                • PS 这并不依赖于太多的 bash-specific-ness - "
                【解决方案15】:

                对于原来的问题,为什么exit命令没有退出,我也遇到了同样的问题,终于找到原因了。

                通过使用 bash 的调试模式,我可以看到退出命令被调用,但进程仍然挂起,直到在“开始”之后再刷新一行到日志文件。奇怪的是,当“开始”出现时,即使退出已被调用,该过程仍然被某些东西所吸引。我猜应该是tail -f,等多出来一行,就真的会解钩了。

                因此,如果您在启动后再打印一行,您的显示器将立即退出。

                【讨论】:

                  【解决方案16】:

                  结合使用答案,我想出了这个简单的解决方案。此示例调用 Tomcat 的 startup.sh 脚本,然后跟踪 catalina.out 日志,直到记录“服务器启动”,然后停止跟踪。

                  #!/bin/bash
                  
                  function logUntilStarted() {
                      tail -n0 -F /home/tomcat/logs/catalina.out | while read line; do
                          if echo $line && echo $line | grep -q 'Server startup' ; then
                              pkill -9 -P $$ tail > /dev/null 2>&1
                          fi
                      done
                  }
                  
                  /home/tomcat/bin/startup.sh
                  logUntilStarted
                  

                  【讨论】:

                    【解决方案17】:

                    使用 nohup 运行上一个命令。

                    在我的情况下,使用 nohup 运行 java -jar,例如

                    nohup java -jar trade.jar xx.jar &
                    

                    不会有日志输出,但会创建一个新的“nohup.out”。原始日志文件 trade.log 也可以。

                    然后,tail -f trade.log,shell 会显示日志信息,Ctrl-c 可以中断它,返回 shell。

                    【讨论】:

                    • 问题(和我的谷歌搜索)正在寻找在没有 shell 访问的情况下完成日志文件时终止该 tail -f 的方法。
                    【解决方案18】:
                    tail -n0 --pid=$(($BASHPID+1)) -F logfile | sed -n '/Started/{s/.*/Server Started/p; q}'
                    

                    管道时,PID 是连续的,所以尾部的 pid 将是 $BASHPID,而 sed 的 pid 将是 $BASHPID+1。当 sed 命令退出时, --pid 开关将导致 tail 退出(正确!)。此 sed 命令将查找 /Started/,然后将整行 (.*) 替换为“Server Started”,然后退出。

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2022-10-14
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-03-04
                      相关资源
                      最近更新 更多