超时在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 

你可以根据你的需要改变SIGINT10 ;)

有点哈克,但它的作品。 如果您有其他的前台进程不起作用(请帮我解决这个问题!)

 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来避免杀死旧进程。