I want to detect when some process is stuck, but it doesn't give me any clues on the matter. All I know is that if it doesn't produce any output in a given timeout (let's say 30 seconds), it means that it's probably blocked.

I know the coreutils' timeout program, but it's based on the full program exit time, not on the last line of output time. I'd love if something like this worked:

timeout --stdout --stderr 30s my-program

So is there any way to do that? How can I do it?

  • I like the idea, but I can't point to an existing tool. This should not be hard to do per se. – tripleee Mar 21 '18 at 13:31
  • You may find this useful. – Wobbly Mar 21 '18 at 14:08

The code

Save this as tkill (make it executable and adjust your PATH if needed):


_terminate_children() {
        trap "exit 143" SIGTERM && kill -- "-$$"

trap _terminate_children SIGINT SIGTERM

eval "$@" | tee >(while :; do
   read -t "$tout"
   case $? in
      0) : ;;
      1) break ;;
      *) _terminate_children ;;
exit "${PIPESTATUS[0]}"

Basic usage

tkill 30 some_command

The first argument (30 here) is the timeout in seconds.


  • tkill expects some_command to generate text (not binary) output.
  • tkill probes stdout of the given command. To include stderr 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 of tkill; tkill 5 true returns 0; tkill 5 false returns 1; tkill 5 "true; false" returns 1.
  • If the given timeout expires or tkill gets interrupted by SIGINT or SIGTERM then the exit status will be 143.

Fragments of code explained

  • eval makes the advanced examples possible.
  • tee allows us to analyze stdin while still passing a copy of it to stdout.
  • 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.


  • 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 |, then tkill 9 ls * would expand * in the current shell, | would appear as an argument to eval and it would be interpreted as a pipe operator. In this case tkill 9 'ls *' is better (but note it expands nothing in the current shell). It's similar with watch (I mean e.g. watch ls * vs watch '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 and tkill 9 'ls --color=auto' give different output.

So, basically something like this:

touch $tmp1
touch $tmp2


typeset -i i

my-program > $tmp1 &

while ps $pgmpid > /dev/null ; do
    sleep 1
    if diff $tmp1 $tmp2 > /dev/null ; then
        if [ $i -gt $time_out ] ; then
            kill $pgmpid
        cp $tmp1 $tmp2 

rm -f $tmp1 $tmp2
  • This should do the trick, although I was expecting some kinda one-liner TBH... Thanks anyway! – Yajo Mar 25 '18 at 19:22

Run the program in the background while copying the output to a file. After 30 seconds, if the file is empty, terminate the program, else bring it back in the foreground.

my-program | tee temp-file & 
sleep 30
[ -s temp-file ] && kill $! || fg $!
  • With this, if after 30 seconds it becomes frozen, you're outta luck, right? – Yajo Mar 25 '18 at 19:21
  • Yeah. Seems I misunderstood the question. – Lubo Kanev Mar 26 '18 at 19:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.