How to kill a program if it did not produce any output in a given timeout?
Solution 1:
The code
Save this as tkill
(make it executable and adjust your PATH
if needed):
#!/bin/bash
_terminate_children() {
trap "exit 143" SIGTERM && kill -- "-$$"
}
trap _terminate_children SIGINT SIGTERM
tout="$1"
shift
eval "$@" | tee >(while :; do
read -t "$tout"
case $? in
0) : ;;
1) break ;;
*) _terminate_children ;;
esac
done)
exit "${PIPESTATUS[0]}"
Basic usage
tkill 30 some_command
The first argument (30
here) is the timeout in seconds.
Notes
-
tkill
expectssome_command
to generate text (not binary) output. -
tkill
probesstdout
of the given command. To includestderr
redirect it like in the last advanced example below.
Advanced usage
These are valid examples:
tkill 9 foo -option value
tkill 9 "foo -option value" # equivalent to the above
tkill 5 "foo | bar"
tkill 5 'foo | bar'
tkill 5 'foo | bar | baz' # tkill monitors baz
tkill 5 'foo | bar' | baz # baz reads from tkill
tkill 3 "foo; bar"
tkill 6 "foo && bar || baz"
tkill 7 "some_command 2>&1"
Use Bash syntax in these quotes.
Exit status
- If
some_command
exits by itself then its exit status will be reused as the exit status oftkill
;tkill 5 true
returns0
;tkill 5 false
returns1
;tkill 5 "true; false"
returns1
. - If the given timeout expires or
tkill
gets interrupted bySIGINT
orSIGTERM
then the exit status will be143
.
Fragments of code explained
-
eval
makes the advanced examples possible. -
tee
allows us to analyzestdin
while still passing a copy of it tostdout
. -
read -t
is responsible for applying the timeout, its exit status is used to determine what to do next. - Command(s) being monitored are killed when needed with this solution.
- Exit status of monitored command(s) is retrieved with this solution.
Quirks
-
eval
makes the advanced examples possible but you need to remember it does this by evaluating its arguments. Example (somewhat artificial): if you had a file literally named|
, thentkill 9 ls *
would expand*
in the current shell,|
would appear as an argument toeval
and it would be interpreted as a pipe operator. In this casetkill 9 'ls *'
is better (but note it expands nothing in the current shell). It's similar withwatch
(I mean e.g.watch ls *
vswatch 'ls *'
). - The evaluated command gets piped to
tee
, it does not write directly to the terminal. Some commands alter their behavior depending on whether their stdout is a terminal or not. They may colorize and/or columnize their output to a terminal, but not their output to a regular file or a pipe. E.g.ls --color=auto
andtkill 9 'ls --color=auto'
give different output.