问题
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 and 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.
回答1:
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 "$@"
回答2:
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
回答3:
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
回答4:
There you go:
timeout --signal=SIGINT 10 /path/to/slow command with options
you may change the SIGINT
and 10
as you desire ;)
回答5:
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.
回答6:
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 theset +b
probably can be left away, too, but better safe than sorry.Except for
--forground
(probably) you can implement all variantstimeout
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.
回答7:
See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils
回答8:
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
回答9:
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
回答10:
To timeout the slowcommand
after 1 second:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
Note the importance of the word "perhaps." With this simple solution, it is possible that an error came from slowcommand
itself and not timeout
. This is meant for quick developmental testing and shouldn't be used in production administration.
回答11:
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
$
回答12:
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.
回答13:
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'
回答14:
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
) }
回答15:
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
回答16:
#! /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 "$@"
回答17:
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
回答18:
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
回答19:
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.
回答20:
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.
回答21:
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.
回答22:
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
来源:https://stackoverflow.com/questions/687948/timeout-a-command-in-bash-without-unnecessary-delay