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

# 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  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 . 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#
 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 
 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 
 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.