Home Using `until` and `/usr/bin/timeout` in a script
Reply: 5

Using `until` and `/usr/bin/timeout` in a script

knocte
1#
knocte Published in 2017-06-21 14:40:51Z

I want to perform a command that takes about 1 minute to finish, in a bash script. However, sometimes this command hangs, so I want to use /usr/bin/timeout inside a loop until it works.

If I use timeout 300 mycommand myarg1, it works, but if I use it inside bash in this loop below, it doesn't print anything (not even the typical output that my command prints) and it hangs!:

until timeout 300 mycommand myarg
do
    echo "The command timed out, trying again..."
done

My version of bash:

$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

My version of timeout:

$ timeout --version
timeout (GNU coreutils) 8.25
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

(Standard Ubuntu16.04)

WLGfx
2#
WLGfx Reply to 2017-12-29 10:00:34Z

I recently had to find a solution to killing a process which wouldn't finish. This is what I managed to come up with:

(mycommand args) & pid=$!
sleep 1000 && kill -INT $pid

Should be self explanatory. Runs your command and grabs the process ID. Kills it after the timeout.

My specific use was for scanning DVB-S:

(w_scan -a 7 -E 0 -f s -c $1 -s $2 -o 7 -x >$tunefile) & pid=$!
sleep 1500 && kill -INT $pid

The w_scan command would never finish and would loop forever scanning the same frequencies.

EDIT: You can check if the pid is still running: This will at least allow your script to act accordingly.

if ps -p $id > /dev/null
then 
    : active
else
    : inactive
fi

EDIT2: I just ran a quick script using ffmpeg to convert from one format to another. My test converted an mkv (2Gb file) to mp4. This would normally take a very long time but I wanted to get it working with just 10 seconds and then exits it.

It wasn't much, but the initial test ran fine.

film=/home/wlgfx/longfilm.mkv
output=/home/wlgfx/longfilm.mp4

(ffmpeg -i $film -y -vcodec copy -acodec copy $output) & pid=$!
#for i in 'seq 1 10' # 10 seconds
#do
#    sleep 1
    if wait $pid; then echo "$pid success $?"
    else echo "$pid fail $?"
    fi
#done

Exit status is $?

I also noticed that ffmpeg only took about 5 seconds to convert the 2Gb file into another container. I'll make an update so that it transcodes and I'll make further changes to the script to reflect them so that it will kill the process after x seconds.

Currently, I'm looking here: https://stackoverflow.com/a/1570351/2979092

EDIT4: By running a separate timeout process, this time, if your process exits properly within the timeout, then the successful exit code will be displayed. Otherwise it will terminate the hanging process.

film=/home/wlgfx/longfilm.mkv
output=/home/wlgfx/longfilm.mp4

while : ; do

    #(ffmpeg -i $film -y -vcodec mpeg2video -acodec copy $output) & pid=$!
    (ffmpeg -i $film -y -vcodec copy -acodec copy $output) & pid=$!

    (sleep 25 ; echo 'timeout'; kill $pid) & killpid=$!

    wait $pid # will get status if completed successfully
    status=$?

    (kill -0 $killpid && kill $killpid) || true

    [[ $status == 0 ]] || break

done
Ljm Dullaart
3#
Ljm Dullaart Reply to 2017-12-22 16:24:12Z

The problem with

(mycommand args) & pid=$!
sleep 1000 && kill -INT $pid

is that it always takes 1000 seconds. Taking a more active (and CPU consuming) approach will shorten that time:

typeset -i i 
i=0
command with arguments &
pid=$!
while ps $pid > /dev/null 2>/dev/null ; do
    i=$i+1
    if [ $i -gt 999 ] ; then
        kill -9 $pid
    fi
    sleep 1
done

Or, if your system is not too busy or the interval is short:

command with arguments &
pid=$! 
echo "kill -9 $pid" | at "now + 15 minutes"
wait

But timeout will also work, of course.

The reason why

until timeout 300 mycommand myarg
do
    echo "The command timed out, trying again..."
done

hangs is that bash will try to evaluate the condition for your until, which may take upto 300 seconds. If timeout 300 mycommand myarg returns a success, the until is never executed. Try for example:

if timeout 30 mycommand myarg ; then
    echo "The timeout of mycommand succeeded"
else
    echo "It failed! What a pitty"
fi 
sorak
4#
sorak Reply to 2017-12-29 04:46:07Z

With this script you can configure the desired timeout as a variable. It launches the process in the background, grabs the process ID, and checks every second to see if the process is still running. If so, a dot is printed, so the process status is very visible. It will report completion of the background thread, and terminate it if it runs longer than desired. The loop in this configuration uses very little overhead.

timeout=300
wait() {
    sleep 1 # sleep for one second
    echo -n . # show that we are still waiting
    [ `ps|grep $pid|wc -l` -gt 0 ] && { # background process still running?
        [ `ps -p $pid -o etimes|grep [0-9]` -gt $timeout ] && { # running too long?
            echo "Time expired.";
            kill $pid;
        } || {
            wait; # keep waiting
        }
    } || {
        echo "DONE!";
    }
}
sleep 20 & # replace this with mycommand &
pid=$!
wait
ctac_
5#
ctac_ Reply to 2017-12-24 09:50:54Z

I can't explain exactly what happen but it seem that when the time expire, timeout use kill which return 0 (success) so at done the shell look at $? and restart the process.

Your echo "The command timed out, trying again..." is exactly what happen.

So try this way.

until timeout 20 find / -name "*.sh" 2>/dev/null ;do
  echo "The command timed out, not trying again..."
  break
done
Qinsi
6#
Qinsi Reply to 2017-12-28 07:11:50Z

This works like a charm.

When command finish:

sh timeoutLoop.sh 4

OUTPUT:

Seconds start now...
3 seconds passed...

When command not finish:

sh timeoutLoop.sh 2

OUTPUT:

Seconds start now...
The command timed out, trying again...
Seconds start now...
The command timed out, trying again...
Seconds start now...
The command timed out, trying again...
Seconds start now...
The command timed out, trying again...
...

Your mycommand = waitSeconds.sh

#!/bin/bash -

echo "Seconds start now..."
sleep $1
echo "$1 seconds passed..."

And the loop bash timeoutLoop.sh

#!/bin/bash -

until timeout $1 waitSeconds.sh 3
do
echo 'The command timed out, trying again...'
done

Try this, and don't forget the header of the script.

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.304191 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO