我有一个bash脚本,它启动了一个子进程,但是这个子进程会不时地崩溃(实际上是挂起),而且没有明显的原因(闭源,所以我无能为力)。因此,我希望能够在给定的时间内启动此进程,并在给定的时间后如果未成功返回则将其终止。
是��有一种简单而稳健的方法可以使用bash实现这一点?
我有一个bash脚本,它启动了一个子进程,但是这个子进程会不时地崩溃(实际上是挂起),而且没有明显的原因(闭源,所以我无能为力)。因此,我希望能够在给定的时间内启动此进程,并在给定的时间后如果未成功返回则将其终止。
是��有一种简单而稳健的方法可以使用bash实现这一点?
(如下所示: BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")
您可以使用timeout
*:
timeout 10 ping www.goooooogle.com
timeout
内部的操作:( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
* 它已经包含在GNU Coreutils 8+中,因此大多数当前的Linux系统已经安装了它,否则您可以安装它,例如sudo apt-get install timeout
或sudo apt-get install coreutils
cmdpid=$BASHPID
将不会获取调用 shell 的 pid,而是由 ()
启动的(第一个)子 shell 的 pid。 (sleep
...的代码段在第一个子 shell 中调用第二个子 shell,在后台等待10秒钟并杀死第一个子 shell,然后第一个子 shell 在启动了杀手子 shell 进程之后继续执行其工作负载... - jamadagnitimeout
是 GNU coreutils 的一部分,因此应该已经安装在所有的 GNU 系统中。 - Sameertimeout
现在是coreutils的一部分。 - benaryorg# 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=$?
kill -9
之前,你应该先尝试进程可以处理的信号。 - Dennis Williamsondosmth
在2秒内终止,另一个进程会获取旧的pid,并且你会杀死新的进程? - Teleporting Goatsleep 999&
t=$!
sleep 10
kill $t
sleep 999
)经常比强制休眠时间(sleep 10
)更快地完成,该怎么办?如果我希望它有机会延长到1分钟、5分钟呢?如果我的脚本中有一堆这样的情况怎么办 :) - it3xl我也有这个问题,并找到了另外两个非常有用的东西:
因此,在命令行上(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”(超时)选项。
read
命令的命名管道与子shell通信。这样,您可以检查正在运行的进程的退出状态,并通过管道将其传递回来。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
假设您已经有(或可以轻松创建)用于跟踪子进程pid的pid文件,然后您可以创建一个脚本来检查pid文件的修改时间,并根据需要杀死/重新启动进程。然后只需将脚本放入crontab中,在大约需要的时间段运行即可。
如果您需要更多细节,请告诉我。如果那听起来不适合您的需求,那么upstart?怎么样?
SIGINT
时,此答案处理信号中断并清理后台进程。它使用了最佳答案中使用的$BASHPID
和exec
技巧来获取进程的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 2
或run_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'
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秒后结束。
这就像其他答案一样,使用后台超时进程在延迟后终止子进程。我认为这几乎与丹的扩展答案(https://dev59.com/8W435IYBdhLWcg3w4UMc#5161274)相同,除了如果已经结束,则不会杀死超时shell。
在此程序结束后,仍将有一些挂起的“sleep”进程运行,但它们应该是无害的。
这可能比我的其他答案更好,因为它不使用非便携式shell功能read -t
,也不使用pgrep
。