2

I need to run a command and kill it when running too long, in a bash script. I also need to capture all output to a variable. If the command finishes first, I need to release/kill the watchdog process (e.g. sleep) because I may run a list of such commands.

Unfortunately the "timeout" command is not available to me, othervise I could do something like this:

output=`timeout -s 9 $TIMEOUT my-command`

and check for the exit code 124 to see if there was a timeout.

Therefore my solution of choice is by @Dmitry to a similar question:

( my_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

Unfortunately the following does not capture anything to the $output:

( output=`my_command` ) & pid=$!

I could dump the output to a file and then load it in the variable like this, but I'd rather do without files:

( `my_command >$outfile` ) & pid=$!
...
output=`cat $outfile`
rm -f $outfile

My question: is there a better way? Ideally capturing the stderr as well to another variable without using files?

Community
  • 1
  • 1
elomage
  • 4,334
  • 2
  • 27
  • 23

1 Answers1

1

Fortunately, the $() notation allows for multiple commands, so you can do this:

output=$(
    ( my_command ) & pid=$!
    ( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
    wait $pid 2>/dev/null && pkill -HUP -P $watcher
)

You can also use regular () to group commands and then redirect all their output. Redirecting stderr to stdout can be done using 2>&1, so you end up with this:

output=$(
    (
        ( my_command ) & pid=$!
        ( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
        wait $pid 2>/dev/null && pkill -HUP -P $watcher
    ) 2>&1
)
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • Grouping is done with curly brackets `{...}`, not parentheses. – gniourf_gniourf Jun 29 '13 at 17:17
  • @gniourf_gniourf: curly braces may work as well, but parentheses do work. – Vaughn Cato Jun 29 '13 at 17:20
  • 1
    Parentheses are for _subshells_. Please see the [_Grouping Commands_ section in the bash reference manual](http://www.gnu.org/software/bash/manual/bashref.html#Command-Grouping) – gniourf_gniourf Jun 29 '13 at 17:22
  • @gniourf_gniourf Cool, I didn't know that. I thought they were the same with the only difference that we had to place a `;` before end squiggly bracket. Good stuff. – jaypal singh Jun 29 '13 at 17:25
  • @gniourf_gniourf: A subshell seems appropriate in this case since there's no need to keep the variables that were used for implementing the timeout functionality. Some may argue it is less efficient to use a subshell, but that's another issue. – Vaughn Cato Jun 29 '13 at 17:27
  • @VaughnCato: Using () captures the output, but exits only when the timeout expires - too long for my_command that finishes fast. – elomage Jun 29 '13 at 17:31
  • @gniourf_gniourf: using grouping {} instead of braces gives a bad substitution error. – elomage Jun 29 '13 at 17:32
  • @elomage You misunderstood where I'm talking about curly brackets. It's not for `$(...)`. It's for the one inside: `output=$(my_command & pid=$!; { sleep ...... ; } > 2>&1 )` – gniourf_gniourf Jun 29 '13 at 17:34
  • @gniourf_gniourf Yes, {} work inside, but the long execution for short commands still remains. If I do not place my_command and sleep in separate subshells, sleep needs to finish before wait && pkill could kill it from the script. – elomage Jun 29 '13 at 17:54
  • @elomage: That's strange. I tried it and if the command finishes before the timeout, I get the output as soon as the command finishes. The logic of the `wait` and `pkill` is supposed to kill the sleep command if the command finishes before the timeout. – Vaughn Cato Jun 30 '13 at 03:43
  • +1 reasonable solution, for moving the conversation forward (debugging the issue), and for sticking with it. Good luck to all. – shellter Jun 30 '13 at 23:00