29

我有以下。

  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
}
4

18 回答 18

27

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

  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
}
于 2010-01-11T11:34:40.150 回答
8

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

它直接处理 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
}
于 2011-01-30T07:42:37.900 回答
7

根据tail 手册页,您可以在 a 进程终止后终止 tail 。

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

tail -f --pid=$! logfile
于 2010-01-11T11:35:07.100 回答
3

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

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
}

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

于 2012-08-20T08:07:16.290 回答
2

捕获后台进程的pid

pid=$!

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

于 2010-01-11T11:35:49.443 回答
1

有一个类似的问题,尾部进程没有被杀死

  1. 运行jsch
  2. tail 没有向 jsch 产生任何输出,因此也没有向其输出流产生任何输出。

使用--pid=$!杀死它并开始一个无限的while循环以在尾巴之前在后台回显某些东西,当底层进程被杀死并因此杀死尾巴时,尾巴会被杀死。

( while true; do echo 'running';  sleep 5; done ) & ( tail -f --pid=$! log-file )
于 2012-07-31T11:13:16.680 回答
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()
于 2011-10-22T05:47:36.810 回答
1

与其退出进程,不如找到进程的进程 IDtail -f并将其终止(kill -9如果您确定日志文件已完成,则此处甚至是安全的)。

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

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

grep -q 'Started' logfile
while [[ $? -ne 0 ]] ; do
    sleep 1
    grep -q 'Started' logfile
done
于 2010-01-11T11:35:28.190 回答
1

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

function startServer() {
  startJavaprocess > logfile &

  while [ 1 ]
  do
   if tail logfile | grep -q 'Started'; then
    echo 'Server started'
    exit 0
   fi
  done
}
于 2010-01-11T11:48:58.023 回答
1

我对这个问题的首选解决方案是将“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,并杀死整个批次——包括它自己。然后祖父母(上面的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())
于 2013-12-03T17:30:41.850 回答
1

可以在后台tail -f logfile,发送tailpidwhile read循环子外壳并trap在 EXIT 上执行以终止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
)
于 2016-03-21T09:41:21.930 回答
0

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

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

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

于 2012-07-12T10:45:40.433 回答
0

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

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

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

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

尾 -f 日志文件 | ( grep -q ; 回显 >> 日志文件)

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

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

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

于 2011-06-27T20:31:31.517 回答
0

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


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)
}
于 2010-01-12T03:26:04.743 回答
0

不要使用 tail - 您可以使用read.

这里我使用FIFO而不是日志文件:

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

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

  echo "Server Started"
}

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

于 2010-01-11T11:44:49.280 回答
0

结合使用答案,我想出了这个简单的解决方案。此示例调用 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
于 2016-03-22T19:58:28.943 回答
0

使用 nohup 运行上一个命令。

就我而言,使用 nohup 运行 java -jar,例如

nohup java -jar trade.jar xx.jar &

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

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

于 2018-08-28T03:53:48.337 回答
-1
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”,然后退出。

于 2017-04-21T16:04:11.837 回答