如何在Bash给定的超时后杀死一个subprocess?

我有一个bash脚本,启动一个subprocess,崩溃(实际上,挂起),不时有没有明显的原因(封闭的来源,所以我没有太多的事情可以做)。 因此,我希望能够在一定的时间内启动这个过程,如果在一定的时间之后没有成功返回,就要杀死它。

有一个简单强大的方法来实现使用bash?

PS:告诉我,如果这个问题更适合serverfault或超级用户。

(如BASH FAQ条目#68所示:“如何运行命令,并在N秒后中止(超时)?” )

如果你不介意下载一些东西,使用timeoutsudo apt-get install timeout ),并使用它:

 timeout 10 ping www.goooooogle.com 

如果你不想下载某些东西,那么在内部做什么超时:

 ( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com ) 

如果你想为更长的bash代码做一个超时,请使用第二个选项:

 ( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) \ & while ! ping -w 1 www.goooooogle.com do echo crap; done ) 
 # Spawn a child process: (dosmth) & pid=$! # in the background, sleep for 10 secs then kill that process (sleep 10 && kill -9 $pid) & 

或者获得退出代码:

 # Spawn a child process: (dosmth) & pid=$! # in the background, sleep for 10 secs then kill that process (sleep 10 && kill -9 $pid) & waiter=$! # wait on our worker process and return the exitcode exitcode=$(wait $pid && echo $?) # kill the waiter subshell, if it still runs kill -9 $waiter 2>/dev/null # 0 if we killed the waiter, cause that means the process finished before the waiter finished_gracefully=$? 
 sleep 999& t=$! sleep 10 kill $t 

我也有这个问题,发现另外两个非常有用的东西:

  1. bash中的SECONDSvariables。
  2. 命令“pgrep”。

所以我在命令行(OSX 10.9)上使用这样的东西:

 ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done 

由于这是一个循环,我包含了一个“睡眠0.2”,以保持CPU凉爽。 😉

(顺便说一下,ping是一个不好的例子,你只要使用内置的“-t”(超时)选项)。

假设你有(或者可以很容易地)创build一个用于跟踪孩子的pid的pid文件,那么你可以创build一个脚本来检查pid文件的modtime,并根据需要杀死/重新生成该进程。 然后把脚本放在crontab中,大概在你需要的时候运行。

让我知道你是否需要更多的细节。 如果这听起来不适合你的需求,那么暴发户呢?

一种方法是在子shell中运行程序,并使用read命令通过命名pipe道与子shell进行通信。 通过这种方式,您可以检查正在运行的进程的退出状态,并通过pipe道传回。

下面yes 3秒后超时的例子。 它使用pgrep获取进程的PID(可能只适用于Linux)。 使用pipe道也有一些问题,打开pipe道进行读取的过程将挂起,直到它也被打开写入,反之亦然。 所以为了防止read命令挂起,我已经“楔住”打开pipe道读背景子shell。 (另一种防止冻结打开pipe道读写的方法,即read -t 5 <>finished.pipe – 但是,除了Linux以外,这也可能不起作用。

 rm -f finished.pipe mkfifo finished.pipe { yes >/dev/null; echo finished >finished.pipe ; } & SUBSHELL=$! # Get command PID while : ; do PID=$( pgrep -P $SUBSHELL yes ) test "$PID" = "" || break sleep 1 done # Open pipe for writing { exec 4>finished.pipe ; while : ; do sleep 1000; done } & read -t 3 FINISHED <finished.pipe if [ "$FINISHED" = finished ] ; then echo 'Subprocess finished' else echo 'Subprocess timed out' kill $PID fi rm finished.pipe 

这里试图避免在一个进程已经退出之后终止一个进程,这样可以减less使用相同进程ID杀死另一个进程的机会(尽pipe完全避免这种错误是不可能的)。

 run_with_timeout () { t=$1 shift echo "running \"$*\" with timeout $t" ( # first, run process in background (exec sh -c "$*") & pid=$! echo $pid # the timeout shell (sleep $t ; echo timeout) & waiter=$! echo $waiter # finally, allow process to end naturally wait $pid echo $? ) \ | (read pid read waiter if test $waiter != timeout ; then read status else status=timeout fi # if we timed out, kill the process if test $status = timeout ; then kill $pid exit 99 else # if the program exited normally, kill the waiting shell kill $waiter exit $status fi ) } 

使用像run_with_timeout 3 sleep 10000 ,运行sleep 10000但3秒后结束。

这就像使用后台超时过程在延迟后终止subprocess的其他答案。 我认为这与Dan的扩展答案( https://stackoverflow.com/a/5161274/1351983 )几乎相同,只是如果超时shell已经结束,则不会终止。

这个程序结束后,仍然会有一些持续的“睡眠”进程在运行,但是它们应该是无害的。

这可能是比我的其他答案更好的解决scheme,因为它不使用不可移植的shellfunctionread -t ,并不使用pgrep

这是我在这里提交的第三个答案。 这个处理信号中断并在收到SIGINT时清除后台进程。 它使用$BASHPIDexec技巧在顶级答案中使用来获取进程的PID(在这种情况下, sh是一个sh调用中的$$ )。 它使用FIFO来与负责查杀和清理的子shell进行通信。 (这就像我的第二个答案pipe道,但有一个命名pipe道意味着信号处理程序也可以写入它。)

 run_with_timeout () { t=$1 ; shift trap cleanup 2 F=$$.fifo ; rm -f $F ; mkfifo $F # first, run main process in background "$@" & pid=$! # sleeper process to time out ( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) & read sleeper <$F # control shell. read from fifo. # final input is "finished". after that # we clean up. we can get a timeout or a # signal first. ( exec 0<$F while : ; do read input case $input in finished) test $sleeper != 0 && kill $sleeper rm -f $F exit 0 ;; timeout) test $pid != 0 && kill $pid sleeper=0 ;; signal) test $pid != 0 && kill $pid ;; esac done ) & # wait for process to end wait $pid status=$? echo finished >$F return $status } cleanup () { echo signal >$$.fifo } 

我尽可能地避免了比赛条件。 然而,我不能删除的一个错误来源是当进程结束与超时相同的时间。 例如, run_with_timeout 2 sleep 2run_with_timeout 0 sleep 0 。 对我来说,后者给出了一个错误:

 timeout.sh: line 250: kill: (23248) - No such process 

因为它试图杀死一个已经退出的进程。