How to kill a program if it did not produce any output in a given timeout?
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.