How to retry a command in Bash? - bash

I have a command that should take less than 1 minute to execute, but for some reason has an extremely long built-in timeout mechanism. I want some bash that does the following:
success = False
try(my_command)
while(!(success))
wait 1 min
if my command not finished
retry(my_command)
else
success = True
end while
How can I do this in Bash?

Look at the GNU timeout command. This kills the process if it has not completed in a given time; you'd simply wrap a loop around this to wait for the timeout to complete successfully, with delays between retries as appropriate, etc.
while timeout -k 70 60 -- my_command; [ $? = 124 ]
do sleep 2 # Pause before retry
done
If you must do it in pure bash (which is not really feasible - bash uses lots of other commands), then you are in for a world of pain and frustration with signal handlers and all sorts of issues.
Please expand on your answer a little. -k 70 is --kill-after= 70 seconds, 124 exit on timeout; what is the 60?
The linked documentation does explain the command; I don't really plan to repeat it all here. The synopsis is timeout [options] duration command [arg]...; one of the options is -k duration. The -k duration says "if the command does not die after the SIGTERM signal is sent at 60 seconds, send a SIGKILL signal at 70 seconds" (and the command should die then). There are a number of documented exit statuses; 124 indicates that the command timed out; 137 that it died after being sent the SIGKILL signal, and so on. You can't tell if the command itself exits with one of the documented statuses.

I found a script from:
http://fahdshariff.blogspot.com/2014/02/retrying-commands-in-shell-scripts.html
#!/bin/bash
# Retries a command on failure.
# $1 - the max number of attempts
# $2... - the command to run
retry() {
local -r -i max_attempts="$1"; shift
local -i attempt_num=1
until "$#"
do
if ((attempt_num==max_attempts))
then
echo "Attempt $attempt_num failed and there are no more attempts left!"
return 1
else
echo "Attempt $attempt_num failed! Trying again in $attempt_num seconds..."
sleep $((attempt_num++))
fi
done
}
# example usage:
retry 5 ls -ltr foo

I liked #Jonathan's answer, but tried to make it more straight forward for future use:
until timeout 1 sleep 2
do
echo "Happening after 1s of sleep"
done

Adapting #Shin's answer to use kill -0 rather than jobs so that this should work even with classic Bourne shell, and allow for other background jobs. You may have to experiment with kill and wait depending on how my_command responds to those.
while true ; do
my_command &
sleep 60
if kill -0 $! 2>/dev/null; then
# Job took too long
kill $!
else
echo "Job is done"
# Reap exit status
wait $!
break
fi
done

You can run a command and retain control with the & background operator. Run your command in the background, sleep for as long as you wish in the foreground, and then, if the background job hasn't terminated, kill it and start over.

while true ; do
my_command &
sleep 60
if [[ $(jobs -r) == "" ]] ; then
echo "Job is done"
break
fi
# Job took too long
kill -9 $!
done

# Retries a given command given number of times and outputs to given variable
# $1 : Command to be passed : handles both simple, piped commands
# $2 : Final output of the command(if successfull)
# $3 : Number of retrial attempts[Default 5]
function retry_function() {
echo "Command to be executed : $1"
echo "Final output variable : $2"
echo "Total trials [Default:5] : $3"
counter=${3:-5}
local _my_output_=$2 #make sure passed variable is not same as this
i=1
while [ $i -le $counter ]; do
local my_result=$(eval "$1")
# this tests if output variable is populated and accordingly retries,
# Not possible to provide error status/logs(STDIN,STDERR)-owing to subshell execution of command
# if error logs are needed, execute the same code, outside function in same shell
if test -z "$my_result"
then
echo "Trial[$i/$counter]: Execution failed"
else
echo "Trial[$i/$counter]: Successfull execution"
eval $_my_output_="'$my_result'"
break
fi
let i+=1
done
}
retry_function "ping -c 4 google.com | grep \"min/avg/max\" | awk -F\"/\" '{print \$5}'" avg_rtt_time
echo $avg_rtt_time
- To pass in a lengthy command, pass a method echoing the content. Take care of method expansion accordingly in a subshell at appropriate place.
- Wait time can be added too - just before the increment!
- For a complex command, youll have to take care of stringifying it(Good luck)

Related

How can you kill a task in bash when it times out? (mosquitto_sub) [duplicate]

This answer to Command line command to auto-kill a command after a certain amount of time
proposes a 1-line method to timeout a long-running command from the bash command line:
( /path/to/slow command with options ) & sleep 5 ; kill $!
But it's possible that a given "long-running" command may finish earlier than the timeout.
(Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)
So this nifty 1-liner approach has a couple of problems.
First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable.
Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.
So...
Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that
has a bash implementation (the other question already has Perl and C answers)
will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
doesn't have to be a 1-liner
can run under Cygwin or Linux
... and, for bonus points
runs the tlrbsf command in the foreground
any 'sleep' or extra process in the background
such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?
If so, please share your code. If not, please explain why.
I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.
You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details.
Here's an example:
timeout 5 /path/to/slow/command with options
I think this is precisely what you are asking for:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.
# Hello Chet,
# please find attached a "little easier" :-) to comprehend
# time-out example. If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <Dmitry.Golovashkin#sas.com>
scriptName="${0##*/}"
declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1
# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY
function printUsage() {
cat <<EOF
Synopsis
$scriptName [-t timeout] [-i interval] [-d delay] command
Execute a command with a time-out.
Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
signal is blocked, then the subsequent SIGKILL (9) terminates it.
-t timeout
Number of seconds to wait for command completion.
Default value: $DEFAULT_TIMEOUT seconds.
-i interval
Interval between checks if the process is still alive.
Positive integer, default value: $DEFAULT_INTERVAL seconds.
-d delay
Delay between posting the SIGTERM signal and destroying the
process by SIGKILL. Default value: $DEFAULT_DELAY seconds.
As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}
# Options.
while getopts ":t:i:d:" option; do
case "$option" in
t) timeout=$OPTARG ;;
i) interval=$OPTARG ;;
d) delay=$OPTARG ;;
*) printUsage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
printUsage
exit 1
fi
# kill -0 pid Exit code indicates if a signal may be sent to $pid process.
(
((t = timeout))
while ((t > 0)); do
sleep $interval
kill -0 $$ || exit 0
((t -= interval))
done
# Be nice, post SIGTERM first.
# The 'exit 0' below will be executed if any preceeding command fails.
kill -s SIGTERM $$ && kill -0 $$ || exit 0
sleep $delay
kill -s SIGKILL $$
) 2> /dev/null &
exec "$#"
This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command
#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.
Examples:
your_command runs more than 2 seconds and was terminated
your_command interrupted
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
your_command finished before the timeout (20 seconds)
your_command finished
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
To timeout the slowcommand after 1 second:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
To determine whether the command timed out or failed for its own reasons, check whether the status code is 124:
# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS
Note that when the exit status is 124, you don't know whether it timed out due to your timeout command, or whether the command itself terminated due to some internal timeout logic of its own and then returned 124. You can safely assume in either case, though, that a timeout of some kind happened.
There you go:
timeout --signal=SIGINT 10 /path/to/slow command with options
you may change the SIGINT and 10 as you desire ;)
You can do this entirely with bash 4.3 and above:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
Example: _timeout 5 longrunning_command args
Example: { _timeout 5 producer || echo KABOOM $?; } | consumer
Example: producer | { _timeout 5 consumer1; consumer2; }
Example: { while date; do sleep .3; done; } | _timeout 5 cat | less
Needs Bash 4.3 for wait -n
Gives 137 if the command was killed, else the return value of the command.
Works for pipes. (You do not need to go foreground here!)
Works with internal shell commands or functions, too.
Runs in a subshell, so no variable export into the current shell, sorry.
If you do not need the return code, this can be made even simpler:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; kill -9 `jobs -p`; ) }
Notes:
Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.
Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)
This recipe can be used "naturally" in the shell (as natural as for flock fd):
(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)
However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.
Edit:
Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$#"; }; _timeout 0.3 __orig__git_ps1 "$#"; ) }
Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.
Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.
Edit4: Forgot a _ in the first _timeout for the real world GIT example.
I prefer "timelimit", which has a package at least in debian.
http://devel.ringlet.net/sysutils/timelimit/
It is a bit nicer than the coreutils "timeout" because it prints something when killing the process, and it also sends SIGKILL after some time by default.
See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils
timeout is probably the first approach to try. You may need notification or another command to execute if it times out. After quite a bit of searching and experimenting, I came up with this bash script:
if
timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
echo 'OK'; #if you want a positive response
else
echo 'Not OK';
AND_ALTERNATIVE_COMMANDS
fi
Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Actually, I think you can reverse it, meeting your 'bonus' criteria:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Simple script with code clarity. Save to /usr/local/bin/run:
#!/bin/bash
# run
# Run command with timeout $1 seconds.
# Timeout seconds
timeout_seconds="$1"
shift
# PID
pid=$$
# Start timeout
(
sleep "$timeout_seconds"
echo "Timed out after $timeout_seconds seconds"
kill -- -$pid &>/dev/null
) &
timeout_pid=$!
# Run
"$#"
# Stop timeout
kill $timeout_pid &>/dev/null
Times out a command that runs too long:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Ends immediately for a command that completes:
$ run 10 sleep 2
$
If you already know the name of the program (let's assume program) to terminate after the timeout (as an example 3 seconds), I can contribute a simple and somewhat dirty alternative solution:
(sleep 3 && killall program) & ./program
This works perfectly if I call benchmark processes with system calls.
There's also cratimeout by Martin Cracauer (written in C for Unix and Linux systems).
# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
OS X doesn't use bash 4 yet, nor does it have /usr/bin/timeout, so here's a function that works on OS X without home-brew or macports that is similar to /usr/bin/timeout (based on Tino's answer). Parameter validation, help, usage, and support for other signals are an exercise for reader.
# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
set -m +b
sleep "$1" &
SPID=${!}
("${#:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
CPID=${!}
wait %1
SLEEPRETVAL=$?
if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
RETVAL=124
# When you need to make sure it dies
#(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
wait %2
else
wait %2
RETVAL=$?
fi
return $RETVAL
) }
Here is a version that does not rely on spawning a child process - I needed a standalone script which embedded this functionality. It also does a fractional poll interval, so you can poll quicker. timeout would have been preferred - but I'm stuck on an old server
# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
local timeout=$1; shift
local interval=$1; shift
$* &
local child=$!
loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
((t = loops))
while ((t > 0)); do
sleep $interval
kill -0 $child &>/dev/null || return
((t -= 1))
done
kill $child &>/dev/null || kill -0 $child &>/dev/null || return
sleep $interval
kill -9 $child &>/dev/null
echo Timed out
}
slow_command()
{
sleep 2
echo Completed normally
}
# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command
# or call an external command
wait_on_command 1 0.1 sleep 10
The timeout command itself has a --foreground option. This lets the command interact with the user "when not running timeout directly from a shell prompt".
timeout --foreground the_command its_options
I think the questioner must have been aware of the very obvious solution of the timeout command, but asked for an alternate solution for this reason. timeout did not work for me when I called it using popen, i.e. 'not directly from the shell'. However, let me not assume that this may have been the reason in the questioner's case. Take a look at its man page.
If you want to do it in your script, put this in there:
parent=$$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
I was presented with a problem to preserve the shell context and allow timeouts, the only problem with it is it will stop script execution on the timeout - but it's fine with the needs I was presented:
#!/usr/bin/env bash
safe_kill()
{
ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}
my_timeout()
{
typeset _my_timeout _waiter_pid _return
_my_timeout=$1
echo "Timeout($_my_timeout) running: $*"
shift
(
trap "return 0" USR1
sleep $_my_timeout
echo "Timeout($_my_timeout) reached for: $*"
safe_kill $$
) &
_waiter_pid=$!
"$#" || _return=$?
safe_kill $_waiter_pid -USR1
echo "Timeout($_my_timeout) ran: $*"
return ${_return:-0}
}
my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd
with the outputs:
Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated
of course I assume there was a dir called scripts
#! /bin/bash
timeout=10
interval=1
delay=3
(
((t = timeout)) || :
while ((t > 0)); do
echo "$t"
sleep $interval
# Check if the process still exists.
kill -0 $$ 2> /dev/null || exit 0
((t -= interval)) || :
done
# Be nice, post SIGTERM first.
{ echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &
exec "$#"
My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.
I now use the following :
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
This way the command returns 255 when there was a timeout or the returncode of the command in case of success
Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell
Building on #loup's answer...
If you want to timeout a process and silence the kill job/pid output, run:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
This puts the backgrounded process into a subshell so you don't see the job output.
A very simplistic way:
# command & sleep 5; pkill -9 -x -f "command"
with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.
In 99% of the cases the answer is NOT to implement any timeout logic. Timeout logic is in nearly any situation a red warning sign that something else is wrong and should be fixed instead.
Is your process hanging or breaking after n seconds sometimes? Then find out why and fix that instead.
As an aside, to do strager's solution right, you need to use wait "$SPID" instead of fg 1, since in scripts you don't have job control (and trying to turn it on is stupid). Moreover, fg 1 relies on the fact that you didn't start any other jobs previously in the script which is a bad assumption to make.
I have a cron job that calls a php script and, some times, it get stuck on php script. This solution was perfect to me.
I use:
scripttimeout -t 60 /script.php

Is there a way to add a 60 second time limit to a command? [duplicate]

This answer to Command line command to auto-kill a command after a certain amount of time
proposes a 1-line method to timeout a long-running command from the bash command line:
( /path/to/slow command with options ) & sleep 5 ; kill $!
But it's possible that a given "long-running" command may finish earlier than the timeout.
(Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)
So this nifty 1-liner approach has a couple of problems.
First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable.
Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.
So...
Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that
has a bash implementation (the other question already has Perl and C answers)
will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
doesn't have to be a 1-liner
can run under Cygwin or Linux
... and, for bonus points
runs the tlrbsf command in the foreground
any 'sleep' or extra process in the background
such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?
If so, please share your code. If not, please explain why.
I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.
You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details.
Here's an example:
timeout 5 /path/to/slow/command with options
I think this is precisely what you are asking for:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.
# Hello Chet,
# please find attached a "little easier" :-) to comprehend
# time-out example. If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <Dmitry.Golovashkin#sas.com>
scriptName="${0##*/}"
declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1
# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY
function printUsage() {
cat <<EOF
Synopsis
$scriptName [-t timeout] [-i interval] [-d delay] command
Execute a command with a time-out.
Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
signal is blocked, then the subsequent SIGKILL (9) terminates it.
-t timeout
Number of seconds to wait for command completion.
Default value: $DEFAULT_TIMEOUT seconds.
-i interval
Interval between checks if the process is still alive.
Positive integer, default value: $DEFAULT_INTERVAL seconds.
-d delay
Delay between posting the SIGTERM signal and destroying the
process by SIGKILL. Default value: $DEFAULT_DELAY seconds.
As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}
# Options.
while getopts ":t:i:d:" option; do
case "$option" in
t) timeout=$OPTARG ;;
i) interval=$OPTARG ;;
d) delay=$OPTARG ;;
*) printUsage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
printUsage
exit 1
fi
# kill -0 pid Exit code indicates if a signal may be sent to $pid process.
(
((t = timeout))
while ((t > 0)); do
sleep $interval
kill -0 $$ || exit 0
((t -= interval))
done
# Be nice, post SIGTERM first.
# The 'exit 0' below will be executed if any preceeding command fails.
kill -s SIGTERM $$ && kill -0 $$ || exit 0
sleep $delay
kill -s SIGKILL $$
) 2> /dev/null &
exec "$#"
This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command
#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.
Examples:
your_command runs more than 2 seconds and was terminated
your_command interrupted
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
your_command finished before the timeout (20 seconds)
your_command finished
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
To timeout the slowcommand after 1 second:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
To determine whether the command timed out or failed for its own reasons, check whether the status code is 124:
# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS
Note that when the exit status is 124, you don't know whether it timed out due to your timeout command, or whether the command itself terminated due to some internal timeout logic of its own and then returned 124. You can safely assume in either case, though, that a timeout of some kind happened.
There you go:
timeout --signal=SIGINT 10 /path/to/slow command with options
you may change the SIGINT and 10 as you desire ;)
You can do this entirely with bash 4.3 and above:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
Example: _timeout 5 longrunning_command args
Example: { _timeout 5 producer || echo KABOOM $?; } | consumer
Example: producer | { _timeout 5 consumer1; consumer2; }
Example: { while date; do sleep .3; done; } | _timeout 5 cat | less
Needs Bash 4.3 for wait -n
Gives 137 if the command was killed, else the return value of the command.
Works for pipes. (You do not need to go foreground here!)
Works with internal shell commands or functions, too.
Runs in a subshell, so no variable export into the current shell, sorry.
If you do not need the return code, this can be made even simpler:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; kill -9 `jobs -p`; ) }
Notes:
Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.
Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)
This recipe can be used "naturally" in the shell (as natural as for flock fd):
(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)
However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.
Edit:
Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$#"; }; _timeout 0.3 __orig__git_ps1 "$#"; ) }
Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.
Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.
Edit4: Forgot a _ in the first _timeout for the real world GIT example.
I prefer "timelimit", which has a package at least in debian.
http://devel.ringlet.net/sysutils/timelimit/
It is a bit nicer than the coreutils "timeout" because it prints something when killing the process, and it also sends SIGKILL after some time by default.
See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils
timeout is probably the first approach to try. You may need notification or another command to execute if it times out. After quite a bit of searching and experimenting, I came up with this bash script:
if
timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
echo 'OK'; #if you want a positive response
else
echo 'Not OK';
AND_ALTERNATIVE_COMMANDS
fi
Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Actually, I think you can reverse it, meeting your 'bonus' criteria:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Simple script with code clarity. Save to /usr/local/bin/run:
#!/bin/bash
# run
# Run command with timeout $1 seconds.
# Timeout seconds
timeout_seconds="$1"
shift
# PID
pid=$$
# Start timeout
(
sleep "$timeout_seconds"
echo "Timed out after $timeout_seconds seconds"
kill -- -$pid &>/dev/null
) &
timeout_pid=$!
# Run
"$#"
# Stop timeout
kill $timeout_pid &>/dev/null
Times out a command that runs too long:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Ends immediately for a command that completes:
$ run 10 sleep 2
$
If you already know the name of the program (let's assume program) to terminate after the timeout (as an example 3 seconds), I can contribute a simple and somewhat dirty alternative solution:
(sleep 3 && killall program) & ./program
This works perfectly if I call benchmark processes with system calls.
There's also cratimeout by Martin Cracauer (written in C for Unix and Linux systems).
# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
OS X doesn't use bash 4 yet, nor does it have /usr/bin/timeout, so here's a function that works on OS X without home-brew or macports that is similar to /usr/bin/timeout (based on Tino's answer). Parameter validation, help, usage, and support for other signals are an exercise for reader.
# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
set -m +b
sleep "$1" &
SPID=${!}
("${#:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
CPID=${!}
wait %1
SLEEPRETVAL=$?
if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
RETVAL=124
# When you need to make sure it dies
#(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
wait %2
else
wait %2
RETVAL=$?
fi
return $RETVAL
) }
Here is a version that does not rely on spawning a child process - I needed a standalone script which embedded this functionality. It also does a fractional poll interval, so you can poll quicker. timeout would have been preferred - but I'm stuck on an old server
# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
local timeout=$1; shift
local interval=$1; shift
$* &
local child=$!
loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
((t = loops))
while ((t > 0)); do
sleep $interval
kill -0 $child &>/dev/null || return
((t -= 1))
done
kill $child &>/dev/null || kill -0 $child &>/dev/null || return
sleep $interval
kill -9 $child &>/dev/null
echo Timed out
}
slow_command()
{
sleep 2
echo Completed normally
}
# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command
# or call an external command
wait_on_command 1 0.1 sleep 10
The timeout command itself has a --foreground option. This lets the command interact with the user "when not running timeout directly from a shell prompt".
timeout --foreground the_command its_options
I think the questioner must have been aware of the very obvious solution of the timeout command, but asked for an alternate solution for this reason. timeout did not work for me when I called it using popen, i.e. 'not directly from the shell'. However, let me not assume that this may have been the reason in the questioner's case. Take a look at its man page.
If you want to do it in your script, put this in there:
parent=$$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
I was presented with a problem to preserve the shell context and allow timeouts, the only problem with it is it will stop script execution on the timeout - but it's fine with the needs I was presented:
#!/usr/bin/env bash
safe_kill()
{
ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}
my_timeout()
{
typeset _my_timeout _waiter_pid _return
_my_timeout=$1
echo "Timeout($_my_timeout) running: $*"
shift
(
trap "return 0" USR1
sleep $_my_timeout
echo "Timeout($_my_timeout) reached for: $*"
safe_kill $$
) &
_waiter_pid=$!
"$#" || _return=$?
safe_kill $_waiter_pid -USR1
echo "Timeout($_my_timeout) ran: $*"
return ${_return:-0}
}
my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd
with the outputs:
Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated
of course I assume there was a dir called scripts
#! /bin/bash
timeout=10
interval=1
delay=3
(
((t = timeout)) || :
while ((t > 0)); do
echo "$t"
sleep $interval
# Check if the process still exists.
kill -0 $$ 2> /dev/null || exit 0
((t -= interval)) || :
done
# Be nice, post SIGTERM first.
{ echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &
exec "$#"
My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.
I now use the following :
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
This way the command returns 255 when there was a timeout or the returncode of the command in case of success
Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell
Building on #loup's answer...
If you want to timeout a process and silence the kill job/pid output, run:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
This puts the backgrounded process into a subshell so you don't see the job output.
A very simplistic way:
# command & sleep 5; pkill -9 -x -f "command"
with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.
In 99% of the cases the answer is NOT to implement any timeout logic. Timeout logic is in nearly any situation a red warning sign that something else is wrong and should be fixed instead.
Is your process hanging or breaking after n seconds sometimes? Then find out why and fix that instead.
As an aside, to do strager's solution right, you need to use wait "$SPID" instead of fg 1, since in scripts you don't have job control (and trying to turn it on is stupid). Moreover, fg 1 relies on the fact that you didn't start any other jobs previously in the script which is a bad assumption to make.
I have a cron job that calls a php script and, some times, it get stuck on php script. This solution was perfect to me.
I use:
scripttimeout -t 60 /script.php

Bash skip sleep and go to next loop iteration

I have a bash loop that looks like this:
something(){
echo "something"
}
while true; do
something
sleep 10s
done | otherCommand
When the loop is in the sleep state, I want to be able to be able to run a function from the terminal that will skip the sleep step and go on to the next iteration of the loop.
For example, if the script has been sleeping for 5 seconds, I want the command to stop the script from sleeping, go on to the next iteration of the loop, run something, and continue sleeping again.
This is not foolproof, but may be robust enough:
To interrupt the sleep command, run:
pkill -f 'sleep 10s'
Note that the script running the sleep command prints something like the following to stderr when sleep is killed: <script-path>: line <line-number>: <pid> Terminated: 15 sleep 10s. Curiously, you cannot suppress this script with sleep 10s 2>/dev/null; to suppress it, you have to either apply 2>/dev/null to the while loop as a whole, or to the script invocation as a whole.
As #miken32 points out in the comments, this command tries to kill all commands whose invocation command line matches sleep 10s, though - unless you're running as root - killing will only succeed for matches among your processed due to lack of permission to kill other users' processes.
To be safer, you can explicitly restrict matches to your own processes:
pkill -u "$(id -u)" -f 'sleep 10s'
Truly safe use, however, requires that you capture your running script's PID (process ID), save it to a file, and use the PID from that file with pkill's -P option, which limits matches to child processes of the specified PID - see this answer (thanks, #miken32).
If you want to skip the sleep, use something like a file you can touch:
if [ ! -f /tmp/skipsleep ]; then
sleep 10
fi
When you want to interrupt the sleep command, kill it!
You could read from a named pipe with read -t 10 instead of sleeping: props for named pipe added by "that other guy"
something()
{
echo "something"
}
somethingelse()
{
echo "something else"
}
mkfifo ~/.myfifo
while cat ~/.myfifo; do true; done |
while true
do
something
read -t 10 && somethingelse
done
Now whenever another script writes to the fifo with echo > ~/.myfifo, the loop will skip its current wait and continue to the next iteration.
This way, different users or different scripts waiting ten seconds will not interfere with each other.
Solution below works if script is running in foreground and you are waiting. Of course you would need a valid loop exit condition. You can also check the value entered and act on it differently. In this case pressing any key except 'j' will iterate the loop. Pressing 'j' will pipe the output of somethingelse to awk.
something()
{
echo "something"
}
somethingelse()
{
echo "something else"
}
while true; do
something |awk '{print "piping something: " $0 }'
read -t 3 -s -n 1 answer
if [ $? == 0 ]; then
echo "you didn't want to wait!"
fi
if [ "$answer" = "j" ]; then
somethingelse | awk '{print "piping something else: " $0 }'
fi
done
If its interactive then just use "read -t #" instead of "sleep #".
You can then just press enter to skip the timeout.
This may be old, but a very simple, elegant solution is to iterate a non existant, empty or 0 set variable, while checking if it has some value first. In this case the ternary operator works as intended
for((;;)){
# skip an interation
(( i ))&& do something || i=1
# skip 5 iterations
(( n >= 5 ))&& do something ||
((n++))
}

How do you run multiple programs in parallel from a bash script?

I am trying to write a .sh file that runs many programs simultaneously
I tried this
prog1
prog2
But that runs prog1 then waits until prog1 ends and then starts prog2...
So how can I run them in parallel?
How about:
prog1 & prog2 && fg
This will:
Start prog1.
Send it to background, but keep printing its output.
Start prog2, and keep it in foreground, so you can close it with ctrl-c.
When you close prog2, you'll return to prog1's foreground, so you can also close it with ctrl-c.
To run multiple programs in parallel:
prog1 &
prog2 &
If you need your script to wait for the programs to finish, you can add:
wait
at the point where you want the script to wait for them.
If you want to be able to easily run and kill multiple process with ctrl-c, this is my favorite method: spawn multiple background processes in a (…) subshell, and trap SIGINT to execute kill 0, which will kill everything spawned in the subshell group:
(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)
You can have complex process execution structures, and everything will close with a single ctrl-c (just make sure the last process is run in the foreground, i.e., don't include a & after prog1.3):
(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
If there is a chance the last command might exit early and you want to keep everything else running, add wait as the last command. In the following example, sleep 2 would have exited first, killing sleep 4 before it finished; adding wait allows both to run to completion:
(trap 'kill 0' SIGINT; sleep 4 & sleep 2 & wait)
You can use wait:
some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2
It assigns the background program PIDs to variables ($! is the last launched process' PID), then the wait command waits for them. It is nice because if you kill the script, it kills the processes too!
With GNU Parallel http://www.gnu.org/software/parallel/ it is as easy as:
(echo prog1; echo prog2) | parallel
Or if you prefer:
parallel ::: prog1 prog2
Learn more:
Watch the intro video for a quick introduction:
https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Walk through the tutorial (man parallel_tutorial). Your command line
will love you for it.
Read: Ole Tange, GNU Parallel 2018 (Ole Tange, 2018).
xargs -P <n> allows you to run <n> commands in parallel.
While -P is a nonstandard option, both the GNU (Linux) and macOS/BSD implementations support it.
The following example:
runs at most 3 commands in parallel at a time,
with additional commands starting only when a previously launched process terminates.
time xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
The output looks something like:
1 # output from 1st command
4 # output from *last* command, which started as soon as the count dropped below 3
2 # output from 2nd command
3 # output from 3rd command
real 0m3.012s
user 0m0.011s
sys 0m0.008s
The timing shows that the commands were run in parallel (the last command was launched only after the first of the original 3 terminated, but executed very quickly).
The xargs command itself won't return until all commands have finished, but you can execute it in the background by terminating it with control operator & and then using the wait builtin to wait for the entire xargs command to finish.
{
xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
} &
# Script execution continues here while `xargs` is running
# in the background.
echo "Waiting for commands to finish..."
# Wait for `xargs` to finish, via special variable $!, which contains
# the PID of the most recently started background process.
wait $!
Note:
BSD/macOS xargs requires you to specify the count of commands to run in parallel explicitly, whereas GNU xargs allows you to specify -P 0 to run as many as possible in parallel.
Output from the processes run in parallel arrives as it is being generated, so it will be unpredictably interleaved.
GNU parallel, as mentioned in Ole's answer (does not come standard with most platforms), conveniently serializes (groups) the output on a per-process basis and offers many more advanced features.
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log
Redirect errors to separate logs.
Here is a function I use in order to run at max n process in parallel (n=4 in the example):
max_children=4
function parallel {
local time1=$(date +"%H:%M:%S")
local time2=""
# for the sake of the example, I'm using $2 as a description, you may be interested in other description
echo "starting $2 ($time1)..."
"$#" && time2=$(date +"%H:%M:%S") && echo "finishing $2 ($time1 -- $time2)..." &
local my_pid=$$
local children=$(ps -eo ppid | grep -w $my_pid | wc -w)
children=$((children-1))
if [[ $children -ge $max_children ]]; then
wait -n
fi
}
parallel sleep 5
parallel sleep 6
parallel sleep 7
parallel sleep 8
parallel sleep 9
wait
If max_children is set to the number of cores, this function will try to avoid idle cores.
There is a very useful program that calls nohup.
nohup - run a command immune to hangups, with output to a non-tty
This works beautifully for me (found here):
sh -c 'command1 & command2 & command3 & wait'
It outputs all the logs of each command intermingled (which is what I wanted), and all are killed with ctrl+c.
I had a similar situation recently where I needed to run multiple programs at the same time, redirect their outputs to separated log files and wait for them to finish and I ended up with something like that:
#!/bin/bash
# Add the full path processes to run to the array
PROCESSES_TO_RUN=("/home/joao/Code/test/prog_1/prog1" \
"/home/joao/Code/test/prog_2/prog2")
# You can keep adding processes to the array...
for i in ${PROCESSES_TO_RUN[#]}; do
${i%/*}/./${i##*/} > ${i}.log 2>&1 &
# ${i%/*} -> Get folder name until the /
# ${i##*/} -> Get the filename after the /
done
# Wait for the processes to finish
wait
Source: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/
You can try ppss (abandoned). ppss is rather powerful - you can even create a mini-cluster.
xargs -P can also be useful if you've got a batch of embarrassingly parallel processing to do.
Process Spawning Manager
Sure, technically these are processes, and this program should really be called a process spawning manager, but this is only due to the way that BASH works when it forks using the ampersand, it uses the fork() or perhaps clone() system call which clones into a separate memory space, rather than something like pthread_create() which would share memory. If BASH supported the latter, each "sequence of execution" would operate just the same and could be termed to be traditional threads whilst gaining a more efficient memory footprint. Functionally however it works the same, though a bit more difficult since GLOBAL variables are not available in each worker clone hence the use of the inter-process communication file and the rudimentary flock semaphore to manage critical sections. Forking from BASH of course is the basic answer here but I feel as if people know that but are really looking to manage what is spawned rather than just fork it and forget it. This demonstrates a way to manage up to 200 instances of forked processes all accessing a single resource. Clearly this is overkill but I enjoyed writing it so I kept on. Increase the size of your terminal accordingly. I hope you find this useful.
ME=$(basename $0)
IPC="/tmp/$ME.ipc" #interprocess communication file (global thread accounting stats)
DBG=/tmp/$ME.log
echo 0 > $IPC #initalize counter
F1=thread
SPAWNED=0
COMPLETE=0
SPAWN=1000 #number of jobs to process
SPEEDFACTOR=1 #dynamically compensates for execution time
THREADLIMIT=50 #maximum concurrent threads
TPS=1 #threads per second delay
THREADCOUNT=0 #number of running threads
SCALE="scale=5" #controls bc's precision
START=$(date +%s) #whence we began
MAXTHREADDUR=6 #maximum thread life span - demo mode
LOWER=$[$THREADLIMIT*100*90/10000] #90% worker utilization threshold
UPPER=$[$THREADLIMIT*100*95/10000] #95% worker utilization threshold
DELTA=10 #initial percent speed change
threadspeed() #dynamically adjust spawn rate based on worker utilization
{
#vaguely assumes thread execution average will be consistent
THREADCOUNT=$(threadcount)
if [ $THREADCOUNT -ge $LOWER ] && [ $THREADCOUNT -le $UPPER ] ;then
echo SPEED HOLD >> $DBG
return
elif [ $THREADCOUNT -lt $LOWER ] ;then
#if maxthread is free speed up
SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1-($DELTA/100))"|bc)
echo SPEED UP $DELTA%>> $DBG
elif [ $THREADCOUNT -gt $UPPER ];then
#if maxthread is active then slow down
SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1+($DELTA/100))"|bc)
DELTA=1 #begin fine grain control
echo SLOW DOWN $DELTA%>> $DBG
fi
echo SPEEDFACTOR $SPEEDFACTOR >> $DBG
#average thread duration (total elapsed time / number of threads completed)
#if threads completed is zero (less than 100), default to maxdelay/2 maxthreads
COMPLETE=$(cat $IPC)
if [ -z $COMPLETE ];then
echo BAD IPC READ ============================================== >> $DBG
return
fi
#echo Threads COMPLETE $COMPLETE >> $DBG
if [ $COMPLETE -lt 100 ];then
AVGTHREAD=$(echo "$SCALE;$MAXTHREADDUR/2"|bc)
else
ELAPSED=$[$(date +%s)-$START]
#echo Elapsed Time $ELAPSED >> $DBG
AVGTHREAD=$(echo "$SCALE;$ELAPSED/$COMPLETE*$THREADLIMIT"|bc)
fi
echo AVGTHREAD Duration is $AVGTHREAD >> $DBG
#calculate timing to achieve spawning each workers fast enough
# to utilize threadlimit - average time it takes to complete one thread / max number of threads
TPS=$(echo "$SCALE;($AVGTHREAD/$THREADLIMIT)*$SPEEDFACTOR"|bc)
#TPS=$(echo "$SCALE;$AVGTHREAD/$THREADLIMIT"|bc) # maintains pretty good
#echo TPS $TPS >> $DBG
}
function plot()
{
echo -en \\033[${2}\;${1}H
if [ -n "$3" ];then
if [[ $4 = "good" ]];then
echo -en "\\033[1;32m"
elif [[ $4 = "warn" ]];then
echo -en "\\033[1;33m"
elif [[ $4 = "fail" ]];then
echo -en "\\033[1;31m"
elif [[ $4 = "crit" ]];then
echo -en "\\033[1;31;4m"
fi
fi
echo -n "$3"
echo -en "\\033[0;39m"
}
trackthread() #displays thread status
{
WORKERID=$1
THREADID=$2
ACTION=$3 #setactive | setfree | update
AGE=$4
TS=$(date +%s)
COL=$[(($WORKERID-1)/50)*40]
ROW=$[(($WORKERID-1)%50)+1]
case $ACTION in
"setactive" )
touch /tmp/$ME.$F1$WORKERID #redundant - see main loop
#echo created file $ME.$F1$WORKERID >> $DBG
plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID INIT " good
;;
"update" )
plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID AGE:$AGE" warn
;;
"setfree" )
plot $COL $ROW "Worker$WORKERID: FREE " fail
rm /tmp/$ME.$F1$WORKERID
;;
* )
;;
esac
}
getfreeworkerid()
{
for i in $(seq 1 $[$THREADLIMIT+1])
do
if [ ! -e /tmp/$ME.$F1$i ];then
#echo "getfreeworkerid returned $i" >> $DBG
break
fi
done
if [ $i -eq $[$THREADLIMIT+1] ];then
#echo "no free threads" >> $DBG
echo 0
#exit
else
echo $i
fi
}
updateIPC()
{
COMPLETE=$(cat $IPC) #read IPC
COMPLETE=$[$COMPLETE+1] #increment IPC
echo $COMPLETE > $IPC #write back to IPC
}
worker()
{
WORKERID=$1
THREADID=$2
#echo "new worker WORKERID:$WORKERID THREADID:$THREADID" >> $DBG
#accessing common terminal requires critical blocking section
(flock -x -w 10 201
trackthread $WORKERID $THREADID setactive
)201>/tmp/$ME.lock
let "RND = $RANDOM % $MAXTHREADDUR +1"
for s in $(seq 1 $RND) #simulate random lifespan
do
sleep 1;
(flock -x -w 10 201
trackthread $WORKERID $THREADID update $s
)201>/tmp/$ME.lock
done
(flock -x -w 10 201
trackthread $WORKERID $THREADID setfree
)201>/tmp/$ME.lock
(flock -x -w 10 201
updateIPC
)201>/tmp/$ME.lock
}
threadcount()
{
TC=$(ls /tmp/$ME.$F1* 2> /dev/null | wc -l)
#echo threadcount is $TC >> $DBG
THREADCOUNT=$TC
echo $TC
}
status()
{
#summary status line
COMPLETE=$(cat $IPC)
plot 1 $[$THREADLIMIT+2] "WORKERS $(threadcount)/$THREADLIMIT SPAWNED $SPAWNED/$SPAWN COMPLETE $COMPLETE/$SPAWN SF=$SPEEDFACTOR TIMING=$TPS"
echo -en '\033[K' #clear to end of line
}
function main()
{
while [ $SPAWNED -lt $SPAWN ]
do
while [ $(threadcount) -lt $THREADLIMIT ] && [ $SPAWNED -lt $SPAWN ]
do
WID=$(getfreeworkerid)
worker $WID $SPAWNED &
touch /tmp/$ME.$F1$WID #if this loops faster than file creation in the worker thread it steps on itself, thread tracking is best in main loop
SPAWNED=$[$SPAWNED+1]
(flock -x -w 10 201
status
)201>/tmp/$ME.lock
sleep $TPS
if ((! $[$SPAWNED%100]));then
#rethink thread timing every 100 threads
threadspeed
fi
done
sleep $TPS
done
while [ "$(threadcount)" -gt 0 ]
do
(flock -x -w 10 201
status
)201>/tmp/$ME.lock
sleep 1;
done
status
}
clear
threadspeed
main
wait
status
echo
Since for some reason I can't use wait, I came up with this solution:
# create a hashmap of the tasks name -> its command
declare -A tasks=(
["Sleep 3 seconds"]="sleep 3"
["Check network"]="ping imdb.com"
["List dir"]="ls -la"
)
# execute each task in the background, redirecting their output to a custom file descriptor
fd=10
for task in "${!tasks[#]}"; do
script="${tasks[${task}]}"
eval "exec $fd< <(${script} 2>&1 || (echo $task failed with exit code \${?}! && touch tasks_failed))"
((fd+=1))
done
# print the outputs of the tasks and wait for them to finish
fd=10
for task in "${!tasks[#]}"; do
cat <&$fd
((fd+=1))
done
# determine the exit status
# by checking whether the file "tasks_failed" has been created
if [ -e tasks_failed ]; then
echo "Task(s) failed!"
exit 1
else
echo "All tasks finished without an error!"
exit 0
fi
Your script should look like:
prog1 &
prog2 &
.
.
progn &
wait
progn+1 &
progn+2 &
.
.
Assuming your system can take n jobs at a time. use wait to run only n jobs at a time.
If you're:
On Mac and have iTerm
Want to start various processes that stay open long-term / until Ctrl+C
Want to be able to easily see the output from each process
Want to be able to easily stop a specific process with Ctrl+C
One option is scripting the terminal itself if your use case is more app monitoring / management.
For example I recently did the following. Granted it's Mac specific, iTerm specific, and relies on a deprecated Apple Script API (iTerm has a newer Python option). It doesn't win any elegance awards but gets the job done.
#!/bin/sh
root_path="~/root-path"
auth_api_script="$root_path/auth-path/auth-script.sh"
admin_api_proj="$root_path/admin-path/admin.csproj"
agent_proj="$root_path/agent-path/agent.csproj"
dashboard_path="$root_path/dashboard-web"
osascript <<THEEND
tell application "iTerm"
set newWindow to (create window with default profile)
tell current session of newWindow
set name to "Auth API"
write text "pushd $root_path && $auth_api_script"
end tell
tell newWindow
set newTab to (create tab with default profile)
tell current session of newTab
set name to "Admin API"
write text "dotnet run --debug -p $admin_api_proj"
end tell
end tell
tell newWindow
set newTab to (create tab with default profile)
tell current session of newTab
set name to "Agent"
write text "dotnet run --debug -p $agent_proj"
end tell
end tell
tell newWindow
set newTab to (create tab with default profile)
tell current session of newTab
set name to "Dashboard"
write text "pushd $dashboard_path; ng serve -o"
end tell
end tell
end tell
THEEND
If you have a GUI terminal, you could spawn a new tabbed terminal instance for each process you want to run in parallel.
This has the benefit that each program runs in its own tab where it can be interacted with and managed independently of the other running programs.
For example, on Ubuntu 20.04:
gnome-terminal --tab -- bash -c 'prog1'
gnome-terminal --tab -- bash -c 'prog2'
To run certain programs or other commands sequentially, you can add ;
gnome-terminal --tab -- bash -c 'prog1_1; prog1_2'
gnome-terminal --tab -- bash -c 'prog2'
I've found that for some programs, the terminal closes before they start up. For these programs I append the terminal command with ; wait or ; sleep 1
gnome-terminal --tab -- bash -c 'prog1; wait'
For Mac OS, you would have to find an equivalent command for the terminal you are using - I haven't tested on Mac OS since I don't own a Mac.
There're a lot of interesting answers here, but I took inspiration from this answer and put together a simple script that runs multiple processes in parallel and handles the results once they're done. You can find it in this gist, or below:
#!/usr/bin/env bash
# inspired by https://stackoverflow.com/a/29535256/2860309
pids=""
failures=0
function my_process() {
seconds_to_sleep=$1
exit_code=$2
sleep "$seconds_to_sleep"
return "$exit_code"
}
(my_process 1 0) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 1 second to success"
(my_process 1 1) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 1 second to failure"
(my_process 2 0) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 2 seconds to success"
(my_process 2 1) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 2 seconds to failure"
echo "..."
for pid in $pids; do
if wait "$pid"; then
echo "Process $pid succeeded"
else
echo "Process $pid failed"
failures=$((failures+1))
fi
done
echo
echo "${failures} failures detected"
This results in:
86400: 1 second to success
86401: 1 second to failure
86402: 2 seconds to success
86404: 2 seconds to failure
...
Process 86400 succeeded
Process 86401 failed
Process 86402 succeeded
Process 86404 failed
2 failures detected
With bashj ( https://sourceforge.net/projects/bashj/ ) , you should be able to run not only multiple processes (the way others suggested) but also multiple Threads in one JVM controlled from your script. But of course this requires a java JDK. Threads consume less resource than processes.
Here is a working code:
#!/usr/bin/bashj
#!java
public static int cnt=0;
private static void loop() {u.p("java says cnt= "+(cnt++));u.sleep(1.0);}
public static void startThread()
{(new Thread(() -> {while (true) {loop();}})).start();}
#!bashj
j.startThread()
while [ j.cnt -lt 4 ]
do
echo "bash views cnt=" j.cnt
sleep 0.5
done

Timeout a command in bash without unnecessary delay

This answer to Command line command to auto-kill a command after a certain amount of time
proposes a 1-line method to timeout a long-running command from the bash command line:
( /path/to/slow command with options ) & sleep 5 ; kill $!
But it's possible that a given "long-running" command may finish earlier than the timeout.
(Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)
So this nifty 1-liner approach has a couple of problems.
First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable.
Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.
So...
Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that
has a bash implementation (the other question already has Perl and C answers)
will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
doesn't have to be a 1-liner
can run under Cygwin or Linux
... and, for bonus points
runs the tlrbsf command in the foreground
any 'sleep' or extra process in the background
such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?
If so, please share your code. If not, please explain why.
I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.
You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details.
Here's an example:
timeout 5 /path/to/slow/command with options
I think this is precisely what you are asking for:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.
# Hello Chet,
# please find attached a "little easier" :-) to comprehend
# time-out example. If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <Dmitry.Golovashkin#sas.com>
scriptName="${0##*/}"
declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1
# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY
function printUsage() {
cat <<EOF
Synopsis
$scriptName [-t timeout] [-i interval] [-d delay] command
Execute a command with a time-out.
Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
signal is blocked, then the subsequent SIGKILL (9) terminates it.
-t timeout
Number of seconds to wait for command completion.
Default value: $DEFAULT_TIMEOUT seconds.
-i interval
Interval between checks if the process is still alive.
Positive integer, default value: $DEFAULT_INTERVAL seconds.
-d delay
Delay between posting the SIGTERM signal and destroying the
process by SIGKILL. Default value: $DEFAULT_DELAY seconds.
As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}
# Options.
while getopts ":t:i:d:" option; do
case "$option" in
t) timeout=$OPTARG ;;
i) interval=$OPTARG ;;
d) delay=$OPTARG ;;
*) printUsage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
printUsage
exit 1
fi
# kill -0 pid Exit code indicates if a signal may be sent to $pid process.
(
((t = timeout))
while ((t > 0)); do
sleep $interval
kill -0 $$ || exit 0
((t -= interval))
done
# Be nice, post SIGTERM first.
# The 'exit 0' below will be executed if any preceeding command fails.
kill -s SIGTERM $$ && kill -0 $$ || exit 0
sleep $delay
kill -s SIGKILL $$
) 2> /dev/null &
exec "$#"
This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command
#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.
Examples:
your_command runs more than 2 seconds and was terminated
your_command interrupted
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
your_command finished before the timeout (20 seconds)
your_command finished
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "your_command finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "your_command interrupted"
fi
To timeout the slowcommand after 1 second:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
To determine whether the command timed out or failed for its own reasons, check whether the status code is 124:
# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS
Note that when the exit status is 124, you don't know whether it timed out due to your timeout command, or whether the command itself terminated due to some internal timeout logic of its own and then returned 124. You can safely assume in either case, though, that a timeout of some kind happened.
There you go:
timeout --signal=SIGINT 10 /path/to/slow command with options
you may change the SIGINT and 10 as you desire ;)
You can do this entirely with bash 4.3 and above:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
Example: _timeout 5 longrunning_command args
Example: { _timeout 5 producer || echo KABOOM $?; } | consumer
Example: producer | { _timeout 5 consumer1; consumer2; }
Example: { while date; do sleep .3; done; } | _timeout 5 cat | less
Needs Bash 4.3 for wait -n
Gives 137 if the command was killed, else the return value of the command.
Works for pipes. (You do not need to go foreground here!)
Works with internal shell commands or functions, too.
Runs in a subshell, so no variable export into the current shell, sorry.
If you do not need the return code, this can be made even simpler:
_timeout() { ( set +b; sleep "$1" & "${#:2}" & wait -n; kill -9 `jobs -p`; ) }
Notes:
Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.
Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)
This recipe can be used "naturally" in the shell (as natural as for flock fd):
(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)
However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.
Edit:
Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$#"; }; _timeout 0.3 __orig__git_ps1 "$#"; ) }
Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.
Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.
Edit4: Forgot a _ in the first _timeout for the real world GIT example.
I prefer "timelimit", which has a package at least in debian.
http://devel.ringlet.net/sysutils/timelimit/
It is a bit nicer than the coreutils "timeout" because it prints something when killing the process, and it also sends SIGKILL after some time by default.
See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils
timeout is probably the first approach to try. You may need notification or another command to execute if it times out. After quite a bit of searching and experimenting, I came up with this bash script:
if
timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
echo 'OK'; #if you want a positive response
else
echo 'Not OK';
AND_ALTERNATIVE_COMMANDS
fi
Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Actually, I think you can reverse it, meeting your 'bonus' criteria:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Simple script with code clarity. Save to /usr/local/bin/run:
#!/bin/bash
# run
# Run command with timeout $1 seconds.
# Timeout seconds
timeout_seconds="$1"
shift
# PID
pid=$$
# Start timeout
(
sleep "$timeout_seconds"
echo "Timed out after $timeout_seconds seconds"
kill -- -$pid &>/dev/null
) &
timeout_pid=$!
# Run
"$#"
# Stop timeout
kill $timeout_pid &>/dev/null
Times out a command that runs too long:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Ends immediately for a command that completes:
$ run 10 sleep 2
$
If you already know the name of the program (let's assume program) to terminate after the timeout (as an example 3 seconds), I can contribute a simple and somewhat dirty alternative solution:
(sleep 3 && killall program) & ./program
This works perfectly if I call benchmark processes with system calls.
There's also cratimeout by Martin Cracauer (written in C for Unix and Linux systems).
# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
OS X doesn't use bash 4 yet, nor does it have /usr/bin/timeout, so here's a function that works on OS X without home-brew or macports that is similar to /usr/bin/timeout (based on Tino's answer). Parameter validation, help, usage, and support for other signals are an exercise for reader.
# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
set -m +b
sleep "$1" &
SPID=${!}
("${#:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
CPID=${!}
wait %1
SLEEPRETVAL=$?
if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
RETVAL=124
# When you need to make sure it dies
#(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
wait %2
else
wait %2
RETVAL=$?
fi
return $RETVAL
) }
Here is a version that does not rely on spawning a child process - I needed a standalone script which embedded this functionality. It also does a fractional poll interval, so you can poll quicker. timeout would have been preferred - but I'm stuck on an old server
# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
local timeout=$1; shift
local interval=$1; shift
$* &
local child=$!
loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
((t = loops))
while ((t > 0)); do
sleep $interval
kill -0 $child &>/dev/null || return
((t -= 1))
done
kill $child &>/dev/null || kill -0 $child &>/dev/null || return
sleep $interval
kill -9 $child &>/dev/null
echo Timed out
}
slow_command()
{
sleep 2
echo Completed normally
}
# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command
# or call an external command
wait_on_command 1 0.1 sleep 10
The timeout command itself has a --foreground option. This lets the command interact with the user "when not running timeout directly from a shell prompt".
timeout --foreground the_command its_options
I think the questioner must have been aware of the very obvious solution of the timeout command, but asked for an alternate solution for this reason. timeout did not work for me when I called it using popen, i.e. 'not directly from the shell'. However, let me not assume that this may have been the reason in the questioner's case. Take a look at its man page.
If you want to do it in your script, put this in there:
parent=$$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
I was presented with a problem to preserve the shell context and allow timeouts, the only problem with it is it will stop script execution on the timeout - but it's fine with the needs I was presented:
#!/usr/bin/env bash
safe_kill()
{
ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}
my_timeout()
{
typeset _my_timeout _waiter_pid _return
_my_timeout=$1
echo "Timeout($_my_timeout) running: $*"
shift
(
trap "return 0" USR1
sleep $_my_timeout
echo "Timeout($_my_timeout) reached for: $*"
safe_kill $$
) &
_waiter_pid=$!
"$#" || _return=$?
safe_kill $_waiter_pid -USR1
echo "Timeout($_my_timeout) ran: $*"
return ${_return:-0}
}
my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd
with the outputs:
Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated
of course I assume there was a dir called scripts
#! /bin/bash
timeout=10
interval=1
delay=3
(
((t = timeout)) || :
while ((t > 0)); do
echo "$t"
sleep $interval
# Check if the process still exists.
kill -0 $$ 2> /dev/null || exit 0
((t -= interval)) || :
done
# Be nice, post SIGTERM first.
{ echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &
exec "$#"
My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.
I now use the following :
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
This way the command returns 255 when there was a timeout or the returncode of the command in case of success
Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell
Building on #loup's answer...
If you want to timeout a process and silence the kill job/pid output, run:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
This puts the backgrounded process into a subshell so you don't see the job output.
A very simplistic way:
# command & sleep 5; pkill -9 -x -f "command"
with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.
In 99% of the cases the answer is NOT to implement any timeout logic. Timeout logic is in nearly any situation a red warning sign that something else is wrong and should be fixed instead.
Is your process hanging or breaking after n seconds sometimes? Then find out why and fix that instead.
As an aside, to do strager's solution right, you need to use wait "$SPID" instead of fg 1, since in scripts you don't have job control (and trying to turn it on is stupid). Moreover, fg 1 relies on the fact that you didn't start any other jobs previously in the script which is a bad assumption to make.
I have a cron job that calls a php script and, some times, it get stuck on php script. This solution was perfect to me.
I use:
scripttimeout -t 60 /script.php

Resources