如何在 Bash 中的给定超时后终止子进程?

我有一个 bash 脚本,它启动一个子进程,该进程会不时崩溃(实际上是挂起) ,而且没有明显的原因(源代码是封闭的,所以我对此无能为力)。因此,我希望能够在给定的时间内启动这个过程,如果在给定的时间后它没有成功返回,则终止它。

有没有一个 很简单强壮的方式来实现这一点使用 bash?

附注: 请告诉我这个问题是更适合于服务器错误还是超级用户。

120262 次浏览

假设您有(或者可以很容易地创建)一个用于跟踪子 pid 的 pid 文件,然后可以创建一个脚本来检查 pid 文件的 modtime,并根据需要杀死/重新生成进程。然后将脚本放在 crontab 中,以便在您需要的大约时间内运行。

如果你需要更多的细节,请告诉我。如果这听起来不符合你的需要,那么 暴发户?

sleep 999&
t=$!
sleep 10
kill $t
(见: BASH FAQ 条目 # 68: “如何运行命令,并在 N 秒后终止(超时) ?”)

如果你不介意下载一些东西,使用 timeout(sudo apt-get install timeout) ,并使用它喜欢: (大多数系统已经安装了它,否则使用 sudo apt-get install coreutils)

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=$?

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

下面是一个在3秒后计时 yes命令的示例。它使用 pgrep获取进程的 PID (可能只适用于 Linux)。使用管道还存在一些问题,因为打开管道进行读取的进程将挂起,直到也打开管道进行写入,反之亦然。因此,为了防止 read命令挂起,我用一个后台子 shell“楔形”打开了管道以便读取。(另一种防止冻结打开管道读写的方法,也就是 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

我也有这个问题,并发现另外两件事非常有用:

  1. Bash 中的 SECONDS 变量。
  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

因为这是一个循环,所以我包含了一个“ sleep 0.2”来保持 CPU 凉爽。 ; -)

(顺便说一句,ping 是一个糟糕的示例,您只需要使用内置的“-t”(超时)选项。)

这里有一个尝试,试图避免在进程已经退出后杀死它,这减少了使用相同进程 ID 杀死另一个进程的机会(尽管完全避免这种错误可能是不可能的)。

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秒后结束。

这类似于使用后台超时进程在延迟后终止子进程的其他应答。我认为这几乎与 Dan 的扩展答案(https://stackoverflow.com/a/5161274/1351983)相同,只是超时 shell 在已经结束时不会被终止。

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

这可能是一个比我的其他答案更好的解决方案,因为它不使用非便携式 shell 特性 read -t,也不使用 pgrep

这是我在这里提交的第三个答案。这一个处理信号中断和清理后台进程时,SIGINT接收。它使用 最佳答案中使用的 $BASHPIDexec技巧来获取进程的 PID (在本例中是 sh调用中的 $$)。它使用一个 FIFO 与负责杀死和清理的子 shell 进行通信。(这类似于我的 第二个答案中的管道,但是有一个命名管道意味着信号处理程序也可以写入它。)

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

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

#Kill command after 10 seconds
timeout 10 command


#If you don't have timeout installed, this is almost the same:
sh -c '(sleep 10; kill "$$") & command'


#The same as above, with muted duplicate messages:
sh -c '(sleep 10; kill "$$" 2>/dev/null) & command'