English 中文(简体)
Ending tail -f started in a shell script
原标题:
  • 时间:2010-01-11 11:21:04
  •  标签:
  • bash
  • tail

I have the following.

  1. A Java process writing logs to the stdout
  2. A shell script starting the Java process
  3. Another shell script which executes the previous one and redirects the log
  4. I check the log file with the tail -f command for the success message.

Even if I have exit 0 in the code I cannot end the tail -f process.

Which doesn t let my script to finish. Is there any other way of doing this in Bash?

The code looks like the following.

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
}
最佳回答

The best answer I can come up with is this

  1. Put a timeout on the read, tail -f logfile | read -t 30 line
  2. Start tail with --pid=$$, that way it ll exit when the bash-process has finished.

It ll cover all cases I can think of (server hangs with no output, server exits, server starts correctly).

Dont forget to start your tail before the server.

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

the -F will read the file even if it doesn t exist (start reading it when it appears). The -n0 won t read anything already in the file, so you can keep appending to the logfile instead of overwriting it each time, and to standard log rotation on it.

EDIT:
Ok, so a rather crude solution , if you re using tail. There are probably better solutions using something else but tail, but I got to give it to you, tail gets you out of the broken-pipe quite nicely. A tee which is able to handle SIGPIPE would probably work better. The java process actively doing a file system drop with an im alive message of some sort is probably even easier to wait for.

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
}
问题回答

Based on the answers I found here, this is what I ve come up with.

It directly deals with tail and kills it once we ve seen the needed log output. Using pkill -P $$ tail should ensure that the right process is killed.

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
}

According to the tail man page, you can get tail to terminate after the a process dies

In BASH, you can get the PID of the last started background process using $! SO if you re using bash:

tail -f --pid=$! logfile

I have had a similar situation where I need to tail a log for a "started" message within a reasonable time and if it is not found during that time I need to exit. Here is what I ended up doing.

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
}

I’m not sure this is very elegant or even the best way to do it, but it works good enough for me. I d be happy for any opinions :)

Capture the pid of the background process

pid=$!

Use tail s --pid=PID option, so that it terminates after the process having pid $PID terminates.

Rather than exiting the process, you can instead find the process ID of the tail -f process and kill it (a kill -9 would even be safe here if you re sure the log file has finished).

That way, the while read line will terminate naturally and you won t need to exit.

Or, since you re not really using the tail to output to the screen, you could also try the more old-school:

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

How about using an infinite loop instead of the -f command-line option for tail?

function startServer() {
  startJavaprocess > logfile &

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

I had the same problem, couldn t find simple and good solution. I m not good in Python, but I managed somehow to solve this:

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("
") != -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()

Had a similar issue where the tail process wasnt getting killed when

  1. Run through jsch
  2. tail wasnt producing any output to jsch and hence to its output stream.

Used the --pid=$! to kill it and started a infinite while loop to echo something in the background before the tail which gets killed when the underlying process is killed and thus kills the tail.

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

My preferred solution for this problem is to put the tail command and its consumer into a subshell, and let the filter logic kill the parent and its children (which includes the tail process). If you look at the process tree, it will be:

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

In this approach, the end-marker detection logic (pid 104) looks for its parent PID (102), and all of its children, and kills the whole batch -- including itself. Then the grandparent (pid 101 above) is free to continue.

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

It is possible to background tail -f logfile, send tailpid to the while read loop subshell and implement a trap on EXIT to kill the tail command.

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

This should work and tail should die once the sub shell dies


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

Try this:

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

Don t use tail - you can get the same monitor the newest thing in the file using read.

Here I use a FIFO instead of the log file:

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

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

  echo "Server Started"
}

Note that this leaves a FIFO hanging around.

Using tail -n0 -f piped to grep is indeed a nice solution, and indeed the first process in the pipe will die when it tries to output to a dead grep process.

But if you re hunting for text that appears near to the last current output of the tail, then grep will already have read the whole input from the tail (in one block) and therefore there won t be any more text output in the log that needs sending down the pipe as grep already read it before it quit (or maybe it was already in the pipe buffer) - at least this is my understanding.

Using "-m1" option on grep looks like it d do exactly what you want and leave the input immediately after the line it matched, but it didn t seem to make a difference or help me in my search for similar functionality. I suspect the pipe buffer still holds all the text output from tail, or some other reason for tail not to have anything left to output. You wanted this post-grep-match text still left to be output next, because its what would kill your tail when it tried (still risky - what happens if its the last line for some reason?), and return control to the calling script.

I found one way round it is to output anything into the end of the log file once the grep has quit; ie.

tail -f logfile | ( grep -q ; echo >> logfile)

I have a theory that (if my guess is right) you could force the pipe to be less buffered to make it work without this, or maybe that adding a huponexit setting command to the appropriate pipe component - ie in (probably curly) brackets would help; but I didn t care about appending a blank line to the logfile and it worked ok and its only a smaller test script (so not a long lived logfile that needs to stick to a format for other processing).

shopt -s huponexit would be useful but for the subshell-ness of it.

PS my first post here, woulda liked to do it as a comment to existing answer rather than re-iterate stuff, but I don t think I can now.

For the original question, why the exit command didn t quit, I got the same issue and finally found the cause.

By using the debug mode of bash, I can see the exit command was called but the process still hung until one more line flush to the log file just after the Started . The wierd thing is when Started came out, even exit had been called, the process is still hooked by something. I guess it was tail -f, until one more line come out, it will really release the hook.

So if you print one more line after it s started, your monitor will quit straight away.

Using a combination of answers, I came up with this simple solution. This example calls Tomcat s startup.sh script and then tails the catalina.out log until "Server startup" is logged, and then it stops tailing.

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

Run the previous command with nohup.

In my case, Run java -jar with nohup,such as

nohup java -jar trade.jar xx.jar &

there will no log output,but a new "nohup.out" will created. The original log file trade.log works as well.

Then , tail -f trade.log, the shell will show log info , Ctrl-c can interrupt it ,return to shell.

tail -n0 --pid=$(($BASHPID+1)) -F logfile | sed -n  /Started/{s/.*/Server Started/p; q} 

When piping, PIDs are sequential, so the pid of the tail will be $BASHPID and the pid of the sed will be $BASHPID+1. The --pid switch will cause tail to exit (properly!) when the sed command quits. This sed command will look for /Started/ and then substitute the whole line (.*) with "Server Started", then quit.





相关问题
Parse players currently in lobby

I m attempting to write a bash script to parse out the following log file and give me a list of CURRENT players in the room (so ignoring players that left, but including players that may have rejoined)...

encoding of file shell script

How can I check the file encoding in a shell script? I need to know if a file is encoded in utf-8 or iso-8859-1. Thanks

Bash usage of vi or emacs

From a programming standpoint, when you set the bash shell to use vi or emacs via set -o vi or set -o emacs What is actually going on here? I ve been reading a book where it claims the bash shell ...

Dynamically building a command in bash

I am construcing a command in bash dynamically. This works fine: COMMAND="java myclass" ${COMMAND} Now I want to dynamically construct a command that redirectes the output: LOG=">> myfile.log ...

Perform OR on two hash outputs of sha1sum

I want perform sha1sum file1 and sha1sum file2 and perform bitwise OR operation with them using bash. Output should be printable i.e 53a23bc2e24d039 ... (160 bit) How can I do this? I know echo $(( ...

Set screen-title from shellscript

Is it possible to set the Screen Title using a shell script? I thought about something like sending the key commands ctrl+A shift-A Name enter I searched for about an hour on how to emulate ...

热门标签