tags:

views:

765

answers:

7

What I have is 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 tail -f command for success message.

The problem is 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 ?

Code looks like 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
}
+2  A: 

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
}
roe
what is 30 ? seconds ?
rangalo
it didn't work, the $$ was bash -hB process which is always running
rangalo
I see, I figured you were running a script to start your server, not that you had one doing both start and stop. But anyway, that should be solvable... I'll give it some thought and get back to you
roe
I am currently not on the system, but soon give it a try and let you know.
rangalo
Thanks. This works, but only if I start the script before the tail and loop. Otherwise i only get the timeout no grep.The other disadvantage is that I get output from bash for kill and if wait because the sleep has already died.
rangalo
So far this is the most suitable answer for me.
rangalo
+2  A: 

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
Robert Christie
The problem is the script is waiting. I do the same trick for the shutdown script and it works because the shutdown script finishes. But the startup script hangs on.
rangalo
This will not stop the tail until the server stops, which might be years from now. Use $$ instead, it'll trigger when the script exits.
roe
@roe, can you please elaborate a bit ? My problem is that neither server nor the script is terminating
rangalo
+1  A: 

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
paxdiablo
There might be 'Started' from earlier sessions in there, so I wouldn't bet on your old-school version
roe
No, there won't be because the java process is overwriting the logfile, not appending to it.
paxdiablo
right, I guess I'm not paying attention.. cleaner would be `while ! grep -q 'Started' logfile; do sleep 1; done`
roe
A: 

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.

Alberto Zaccagni
A: 

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.

Alex Brown
This will kill the java process (broken pipe) when the fifo is removed, or in the second example when the loop exits. Probably not what you intended.
roe
true, I had spotted the terminate in the second instance, but not the first. simple fix for the first, I'll remove the second until I think of a way around that.
Alex Brown
A: 

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
}
Flimm
That's not bad but there's a risk you could miss the 'Started' line if, for example, it's written in between loops followed by enough lines to stop it showing up in the next tail.
paxdiablo
If you want to eliminate the risk entirely you could grep in the entire file.
Flimm
A: 
rcarson
this doesn't work. The tail is still alive
rangalo
your right, that is odd...what about this... see edits
rcarson