如何在Bash给定的超时后杀死一个subprocess?
我有一个bash脚本,启动一个subprocess,崩溃(实际上,挂起),不时有没有明显的原因(封闭的来源,所以我没有太多的事情可以做)。 因此,我希望能够在一定的时间内启动这个过程,如果在一定的时间之后没有成功返回,就要杀死它。
有一个简单而强大的方法来实现使用bash?
PS:告诉我,如果这个问题更适合serverfault或超级用户。
(如BASH FAQ条目#68所示:“如何运行命令,并在N秒后中止(超时)?” )
如果你不介意下载一些东西,使用timeout
( sudo 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
我也有这个问题,发现另外两个非常有用的东西:
- bash中的SECONDSvariables。
- 命令“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
时清除后台进程。 它使用$BASHPID
和exec
技巧在顶级答案中使用来获取进程的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 2
或run_with_timeout 0 sleep 0
。 对我来说,后者给出了一个错误:
timeout.sh: line 250: kill: (23248) - No such process
因为它试图杀死一个已经退出的进程。