超时在bash命令没有不必要的延迟
命令行命令的 这个答案 在一段时间后自动杀死一个命令
提出了一个1行方法来从bash命令行中超时一个长时间运行的命令:
( /path/to/slow command with options ) & sleep 5 ; kill $!
但是,一个给定的“长时间运行”命令可能会比超时更早完成。 (让我们把它称为“通常长时间运行但有时是快速的”命令,或者叫做tlrbsf )。
所以这个漂亮的1-liner方法有一些问题。 首先, sleep
不是有条件的,因此设置序列完成所花费的时间的下限。 当tlrbsf命令在2秒钟内完成时, 考虑睡眠30s或2m甚至5m,这是非常不理想的。 其次, kill
是无条件的,所以这个序列将试图杀死一个没有运行的过程,并抱怨它。
所以…
有没有办法超时通常长时间运行,但有时快( “tlrbsf” )命令
- 有一个bash实现(另一个问题已经有Perl和C的答案)
- 将在两者之前终止: tlrbsf程序终止,或者超时
- 不会杀死不存在的/不正在运行的进程(或者,可选地:不会抱怨不好的kill)
- 不一定是一个班轮
- 可以在Cygwin或Linux下运行
…并且对于奖励点,在前台运行tlrbsf命令并且在后台运行任何“睡眠”或额外的进程,使得tlrbsf命令的stdin / stdout / stderr可以被重定向,就像它已经被直接运行?
如果是这样,请分享您的代码。 如果没有,请解释原因。
我花了一段时间试图破解前面提到的例子,但我正在打击我的技能的极限。
我想这正是你所要求的:
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 "$@"
您可能正在寻找coreutils中的timeout
命令。 由于它是coreutils的一部分,它在技术上是一个C的解决方案,但它仍然是coreutils。 info timeout
了解更多详情。 这是一个例子:
timeout 5 /path/to/slow/command with options
不管bash监视器模式如何,此解决方案都可以工 您可以使用正确的信号来终止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
观察者在超时后杀死your_command; 脚本等待缓慢的任务并终止观察者。 请注意, wait
不适用于不同shell的子进程。
例子:
- your_command运行超过2秒钟并终止
your_command中断
( 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在超时之前完成(20秒)
your_command完成
( 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
我更喜欢“timelimit”,它至少在debian中有一个包。
http://devel.ringlet.net/sysutils/timelimit/
它比coreutils的“timeout”更好一些,因为它在杀死进程时会打印一些东西,并且默认情况下会在一段时间后发送SIGKILL。
你可以完全用bash 4.3
及以上来完成这个工作:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
- 例如:
_timeout 5 longrunning_command args
- 例如:
{ _timeout 5 producer || echo KABOOM $?; } | consumer
{ _timeout 5 producer || echo KABOOM $?; } | consumer
- 例如:
producer | { _timeout 5 consumer1; consumer2; }
producer | { _timeout 5 consumer1; consumer2; }
-
例如:
{ while date; do sleep .3; done; } | _timeout 5 cat | less
{ while date; do sleep .3; done; } | _timeout 5 cat | less
-
需要Bash 4.3
wait -n
- 如果命令被杀死,则给出137,否则命令的返回值。
- 适用于管道。 (你不需要在这里前台!)
- 也可以使用内部shell命令或函数。
- 运行在一个子shell中,所以没有变量输出到当前shell中,对不起。
如果你不需要返回代码,这可以变得更简单:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }
笔记:
-
严格地说,你不需要
;
在; )
; )
,但它使事情更加一致; }
; }
case。 而set +b
可能被遗忘,但是比抱歉更安全。 -
除了
--forground
(可能),你可以实现所有变种timeout
支持。 然而,--preserve-status
有点困难。 这是留给读者的练习;)
这个配方可以在外壳中“自然地”使用(与flock fd
一样自然):
( set +b sleep 20 & { YOUR SHELL CODE HERE } & wait -n kill `jobs -p` )
但是,正如上面所解释的那样,您不能以这种方式自然地将环境变量重新导出到封闭的shell中。
编辑:
真实世界的例子:超时__git_ps1
,以防万一需要太长的时间(比如SSHFS-Links慢):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }
编辑2:修正。 我注意到, exit 137
是不需要的,同时使_timeout
不可靠。
编辑3: git
是一个顽固的,所以它需要一个双重技巧,令人满意地工作。
编辑4:在第一个_timeout
为真实世界的GIT例子忘了_
你去了:
timeout --signal=SIGINT 10 /path/to/slow command with options
你可以根据你的需要改变SIGINT
和10
;)
有点哈克,但它的作品。 如果您有其他的前台进程不起作用(请帮我解决这个问题!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
其实,我认为你可以扭转它,满足你的“奖金”标准:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
超时可能是第一个尝试的方法。 如果超时,您可能需要通知或其他命令才能执行。 经过相当多的搜索和试验后,我想出了这个bash脚本:
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
如果你已经知道程序的名字(让我们假设program
)在超时(例如3
秒)后终止,我可以提供一个简单的,有点脏的替代解决方案:
(sleep 3 && killall program) & ./program
如果我使用系统调用调用基准进程,这完美地工作。
还有Martin Cracauer的Cradleout(用C语言编写的Unix和Linux系统)。
# 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'
在99%的情况下,答案是不实现任何超时逻辑。 超时逻辑几乎在任何情况下都是一个红色的警告标志,说明其他的东西是错误的,应该修正。
有时候,你的过程在n秒后是挂起还是断裂? 然后找出原因并改正。
顺便说一下,要做好正确的解决方案,你需要使用等待“$ SPID”而不是fg 1,因为在脚本中你没有任务控制(并试图打开它是愚蠢的)。 而且,fg 1依赖于你以前没有在脚本中开始任何其他工作,这是一个糟糕的假设。
OS X目前还没有使用bash 4,也没有/ usr / bin / timeout,所以这里有一个在OS X下工作的功能,没有home-brew或者macports,类似于/ usr / bin / timeout(基于Tino回答)。 参数验证,帮助,使用和支持其他信号是读者的一个练习。
# 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 ) }
代码清晰的简单脚本。 保存到/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
超时一个运行时间过长的命令:
$ run 2 sleep 10 Timed out after 2 seconds Terminated $
立即结束一个命令完成:
$ run 10 sleep 2 $
我提出了一个问题来保留shell的上下文,并允许超时,唯一的问题是它会停止超时的脚本执行 – 但这是我所提出的需求的罚款:
#!/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
与输出:
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
当然,我认为有一个名为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 "$@"
我的问题也许有点不同:我在远程计算机上通过ssh启动一个命令,如果命令挂起,想杀死shell和childs。
我现在使用以下内容:
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
这样,如果在成功时出现超时或命令的返回码,则该命令返回255
请注意,从ssh会话中处理进程的方式与交互式shell不同。 但是你也可以使用-t选项来分配一个伪终端,所以它就像一个交互式shell
这是一个不依赖派生子进程的版本 – 我需要一个独立的脚本来嵌入这个功能。 它也做一个小数轮询间隔,所以你可以更快轮询。 超时将是首选 – 但我卡在一个旧的服务器上
# 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
一个非常简单的方法:
# command & sleep 5; pkill -9 -x -f "command"
用pkill (选项-f )你可以用参数杀死你的特定命令,或者指定-n来避免杀死旧进程。