结尾tail -f以shell脚本开头

时间:2010-01-11 11:21:04

标签: bash tail

我有以下内容。

  1. Java进程将日志写入stdout
  2. 启动Java进程的shell脚本
  3. 执行上一个shell脚本并重定向日志的另一个shell脚本
  4. 我使用tail -f命令检查日志文件以获取成功消息。
  5. 即使我在代码中有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
    }
    

18 个答案:

答案 0 :(得分:25)

我能想出的最佳答案是

  1. 对阅读tail -f logfile | read -t 30 line
  2. 设置超时
  3. --pid=$$开始尾部,这样当bash-process完成时它将退出。
  4. 它将涵盖我能想到的所有情况(服务器挂起,没有输出,服务器退出,服务器正确启动)。

    别忘了在服务器前启动你的尾巴。

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

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

    修改:
    好吧,如果你使用尾巴,这是一个相当粗糙的“解决方案”。可能有更好的解决方案使用其他东西,但尾巴,但我得给你,尾巴让你从破裂的管道很好。一个能够处理SIGPIPE的'tee'可能会更好。主动执行某种文件系统丢弃的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
    }
    

答案 1 :(得分:7)

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

它直接处理尾部并在我们看到所需的日志输出后将其杀死。使用'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
}

答案 2 :(得分:6)

根据tail man page,您可以在进程终止后终止尾部

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

tail -f --pid=$! logfile

答案 3 :(得分:2)

捕获后台进程的pid

pid=$!

使用tail的 - pid = PID 选项,以便在pid $ PID终止的进程终止后终止。

答案 4 :(得分:2)

我遇到过类似的情况,我需要在合理的时间内为“已启动”的消息留下日志,如果在此期间找不到,我需要退出。这就是我最终要做的事情。

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 :(得分:1)

我遇到了同样的问题,找不到简单而好的解决方案。我不擅长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()

答案 6 :(得分:1)

可以后台tail -f logfile,将tailpid发送到while read循环子shell,并在EXIT上实现trap以终止tail命令。< / p>

( (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
)

答案 7 :(得分:1)

有一个类似的问题,尾部进程在

时没有被杀死
  1. 通过jsch
  2. tail没有产生任何输出到jsch,因此产生输出流。
  3. 使用--pid=$!来杀死它并启动一个无限的while循环来回显尾部之前的东西,当底层进程被杀死并因此杀死尾部时会被杀死。

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

答案 8 :(得分:1)

如何使用无限循环代替尾部的-f命令行选项?

function startServer() {
  startJavaprocess > logfile &

  while [ 1 ]
  do
   if tail logfile | grep -q 'Started'; then
    echo 'Server started'
    exit 0
   fi
  done
}

答案 9 :(得分:1)

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

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

或者,既然你没有真正使用tail输出到屏幕,你也可以尝试更老的学校:

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

答案 10 :(得分:0)

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

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

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

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

tail -f logfile | (grep -q; echo&gt;&gt; logfile)

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

shopt -s huponexit会很有用,但对于它的小部分内容。

PS我在这里的第一篇文章,会喜欢把它作为对现有答案的评论,而不是重复迭代,但我认为我现在不能。

答案 11 :(得分:0)

这应该可以工作,一旦子shell死亡,尾部应该死掉


function startServer() {
  touch logfile
  startJavaprocess > logfile &

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

Try this:

function startServer() {
  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      return 0
    fi
  done < <(startJavaprocess | tee logfile)
}

答案 12 :(得分:0)

对于原始问题,为什么退出命令没有退出,我得到了同样的问题,最后找到了原因。

通过使用bash的调试模式,我可以看到exit命令被调用但是进程仍然挂起,直到在“Started”之后又有一行刷新到日志文件。奇怪的是,当'Started'出现时,即使退出已被调用,该过程仍然被某些东西所吸引。我想这是tail -f,直到另外一行出来,它才真正释放钩子。

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

答案 13 :(得分:0)

不要使用尾巴 - 你可以使用read获得相同的'监控文件中的最新内容'。

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

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

  a=""; while [ "$a" != "Started" ]; do read <logfile a; done

  echo "Server Started"
}

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

答案 14 :(得分:0)

我对此问题的首选解决方案是将'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())

答案 15 :(得分:0)

结合使用答案,我想出了这个简单的解决方案。此示例调用Tomcat的startup.sh脚本,然后关闭catalina.out日志,直到“Server startup”被记录,然后停止拖尾。

#!/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

答案 16 :(得分:0)

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”替换整行(。*),然后退出。

答案 17 :(得分:0)

使用nohup运行上一个命令。

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

nohup java -jar trade.jar xx.jar &

没有日志输出,但是将创建一个新的“ nohup.out”。原始日志文件trade.log也可以正常工作。

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