Kill program after it outputs a given line, from a shell script

15

2

Background:

I am writing a test script for a piece of computational biology software. The software I am testing can take days or even weeks to run, so it has a recover functionality built in, in the case of system crashes or power failures.

I am trying to figure out how to test the recovery system. Specifically, I can't figure out a way to "crash" the program in a controlled manner. I was thinking of somehow timing a SIGKILL instruction to run after some amount of time. This is probably not ideal, as the test case isn't guaranteed to run the same speed every time (it runs in a shared environment), so comparing the logs to desired output would be difficult.

This software DOES print a line for each section of analysis it completes.

Question:

I was wondering if there was a good/elegant way (in a shell script) to capture output from a program and then kill the program when a given line/# of lines is output by the program?

Paul

Posted 2012-03-20T19:39:09.887

Reputation: 253

1

Possible duplicate of "watch" the output of a command until a particular string is observed and then exit

– Ciro Santilli 新疆改造中心法轮功六四事件 – 2018-04-17T22:08:27.163

1Why do you need to stop the program at a specific time? Due to output buffering you may end up killing the program a long time after it outputs the text you are checking for. – stardt – 2012-03-20T20:33:25.347

@stardt I wanted to stop the program at a specific time so that test results were more controlled and reproducible. I have found a way around this now. I will kill the program only once manually, then test only the recovery code. All recovery attempts will start from the same point. I am testing how it recovers, not how it crashes after all :) – Paul – 2012-03-21T17:21:00.150

Answers

7

A wrapper script like this one is a standard approach. The script runs the program in the background and then loops, checking the log file every minute for some string. If the string is found then the background program is killed and the script quits.

command="prog -foo -whatever"
log="prog.log"
match="this is the what i want to match"

$command > "$log" 2>&1 &
pid=$!

while sleep 60
do
    if fgrep --quiet "$match" "$log"
    then
        kill $pid
        exit 0
    fi
done

Kyle Jones

Posted 2012-03-20T19:39:09.887

Reputation: 5 706

14

If your program will terminate on SIGPIPE (which is the default action) it should be enough to pipe the output into a reader that will exit on reading that line.

So it might be as simple as

$ program | sed -e '/Suitable text from the line/q'

If you want to suppress the default output use

$ program | sed -n -e '/Suitable text from the line/q'

Likewise if one wants to stop after a certain number of lines one could use head in place of sed, e.g.

$ program | head -n$NUMBER_OF_LINES_TO_STOP_AFTER

The exact time of at which the kill occurs does depend on the buffering behavior of the terminal as stardt suggests in the comments.

dmckee --- ex-moderator kitten

Posted 2012-03-20T19:39:09.887

Reputation: 7 311

-1. There's a serious flaw here. The program will terminate only when (if) it tries to write to the (broken) pipe after sed terminates. OP says "This software DOES print a line for each section of analysis it completes". It may mean the program will complete one extra section! Proof of concept: { echo 1; sleep 3; >&2 echo peekaboo; sleep 3; echo 2; } | sed -e '/1/q'. See this answer. Possible workaround: ( program & ) | sed -e '/Suitable text from the line/q'; killall program. Make your answer aware of the flaw and I will revoke my downvote.

– Kamil Maciorowski – 2017-08-16T05:52:40.340

Hmmm ... indeed. And the buffering issue makes it unreliable even with your improvement (though, of course, that affects every solution I see here). I like to make use of the signal infrastructure when possible, but at some point it starts to lose it's elegance. Perhaps it's better just to scrap the whole thing. – dmckee --- ex-moderator kitten – 2017-08-16T16:11:57.717

7

As an alternative to dmckee's answer, the grep command with the -m option (see e.g. this man page) command can also be used:

compbio | grep -m 1 "Text to match"

to stop when 1 line matching the text is found or

compbio | grep -v -m 10 "Text to match"

to wait for 10 lines that do not match the given text.

stardt

Posted 2012-03-20T19:39:09.887

Reputation: 243

3

You can use expect(1) to watch a program's stdout then execute predefined action on some patterns.

#!/usr/bin/env expect

spawn your-program -foo -bar 
expect "I want this text" { close }

Or an one-liner for embedding in another script:

expect -c "spawn your-program -foo -bar; expect \"I want this text\" { close }"

Note that expect command doesn't come with every OS by default. You may need to install it.

Jamesits

Posted 2012-03-20T19:39:09.887

Reputation: 131

This is nice solution. I recommend mentioning set timeout -1 to set indefinite timeout, otherwise it will all close under expect's default 10s timeout. – pepper_chico – 2019-09-24T21:23:33.803

1

You could use a "while" statement :

You need to pipe the output of your software (or of its logs) and then, for each new line, make a conditionnal test, then run kill -KILL. For example (I tested with /var/log/messages as the logfile) :

tail -f /var/log/messages | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

Or with the software directly :

./biosoftware | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

You need to remplace the log path/application name, the line to match and the PID to kill (you can also use killall).

Good luck with your software,

Hugo,

pistache

Posted 2012-03-20T19:39:09.887

Reputation: 123