我有一个bash脚本,它会启动一个不时崩溃(实际上是挂起)的子进程,并且没有明显的原因(封闭源代码,因此我无能为力)。因此,我希望能够在给定的时间内启动此过程,如果在给定的时间后没有成功返回,则将其终止。
是否有简单和强大方式来实现使用bash?
P.S。:告诉我这个问题是否更适合服务器故障或超级用户。
答案 0 :(得分:232)
(如下所示: BASH FAQ entry #68: "How do I run a command, and have it abort (timeout) after N seconds?")
如果您不介意下载某些内容,请使用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 )
答案 1 :(得分:26)
# 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=$?
答案 2 :(得分:10)
sleep 999&
t=$!
sleep 10
kill $t
答案 3 :(得分:3)
我也有这个问题,发现还有两件非常有用的东西:
所以我在命令行(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”(超时)选项。)
答案 4 :(得分:1)
假设您已经(或可以轻松制作)用于跟踪孩子的pid的pid文件,您可以创建一个脚本来检查pid文件的modtime并根据需要杀死/重新生成该过程。然后将脚本放在crontab中,大约在你需要的时间内运行。
如果您需要更多详细信息,请与我们联系。如果这听起来不适合您的需求,那么upstart?
呢答案 5 :(得分:1)
一种方法是在子shell中运行程序,并使用read
命令通过命名管道与子shell进行通信。这样,您可以检查正在运行的进程的退出状态,并通过管道将其传回。
这是一个在3秒后超时yes
命令的示例。它使用pgrep
获取进程的PID(可能仅适用于Linux)。使用管道也存在一些问题,因为打开管道进行读取的过程将一直挂起,直到它也被打开以进行写入,反之亦然。因此,为了防止read
命令挂起,我已经&#34;楔入&#34;用后台子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
答案 6 :(得分:0)
这是尝试避免在已经退出后杀死进程的尝试,这减少了使用相同进程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已经结束,则不会被终止。
这个节目结束后,仍然会有一些挥之不去的睡眠&#34;流程运行,但它们应该是无害的。
这可能是比我的其他答案更好的解决方案,因为它不使用非便携式shell功能read -t
而且不使用pgrep
。
答案 7 :(得分:0)
这是我在这里提交的第三个答案。这个处理信号中断并在收到SIGINT
时清理后台进程。它使用top answer中使用的$BASHPID
和exec
技巧来获取进程的PID(在这种情况下$$
调用sh
。它使用FIFO与负责杀死和清理的子shell进行通信。 (这就像我second answer中的管道一样,但是有一个命名管道意味着信号处理程序也可以写入它。)
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
因为它试图杀死一个已经退出的进程。
答案 8 :(得分:0)
#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'