我有一个bash脚本,该脚本会启动一个子进程,该子进程有时会崩溃(实际上是挂起),并且没有明显的原因(封闭源代码,因此我无能为力)。结果,我希望能够在给定的时间内启动此过程,如果在给定的时间之后未成功返回,则将其终止。
有没有简单而强大的方法可以使用bash来实现这一目标?
PS:请告诉我这个问题是否更适合serverfault或超级用户。
我有一个bash脚本,该脚本会启动一个子进程,该子进程有时会崩溃(实际上是挂起),并且没有明显的原因(封闭源代码,因此我无能为力)。结果,我希望能够在给定的时间内启动此过程,如果在给定的时间之后未成功返回,则将其终止。
有没有简单而强大的方法可以使用bash来实现这一目标?
PS:请告诉我这个问题是否更适合serverfault或超级用户。
Answers:
(如下所示: BASH常见问题解答条目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 )
cmdpid=$BASHPID
不会接受调用 shell 的pid,而是由开始的(第一个)子shell ()
。该(sleep
...事情调用第一子shell中的第二子shell等待10秒的背景和杀死第一子shell其中,已经推出了杀手子shell进程后,继续执行其工作量...
timeout
是GNU coreutils的一部分,因此应该已经安装在所有GNU系统中。
timeout
现在已经成为coreutils的一部分。
# 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
在尝试发出可以先处理的信号之前,请勿使用。
dosmth
在2秒内终止,另一进程使用旧的pid,而您杀死了新的pid,该怎么办?
我也有这个问题,发现另外两件事非常有用:
因此,我在命令行(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
命令通过命名管道与子外壳程序通信。这样,您可以检查正在运行的进程的退出状态,并将其通过管道传达回去。
这是一个yes
在3秒钟后使命令超时的示例。它使用获取进程的PID pgrep
(可能仅在Linux上有效)。使用管道还存在一些问题,即打开用于读取的管道的进程将挂起,直到它也被打开以进行写入,反之亦然。因此,为防止read
命令挂起,我“楔住”了一个管道,以便使用背景子外壳进行读取。(另一种防止冻结的操作以读写方式打开管道的方法,即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
这是一种尝试避免退出的进程的尝试,这减少了杀死具有相同进程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
)
}
使用like run_with_timeout 3 sleep 10000
,它会运行,sleep 10000
但会在3秒钟后结束。
就像其他答案一样,这些答案使用后台超时进程在延迟后杀死子进程。我认为这与Dan的扩展答案(https://stackoverflow.com/a/5161274/1351983)几乎相同,除了如果超时shell已经结束,它不会被杀死。
该程序结束后,仍将有一些挥之不去的“睡眠”进程,但它们应该无害。
这可能比我的其他答案更好,因为它不使用非便携式外壳功能read -t
,也不使用pgrep
。
(exec sh -c "$*") &
和之间有什么区别sh -c "$*" &
?具体来说,为什么要使用前者而不是后者?
这是我在这里提交的第三个答案。当SIGINT
接收到信号时,这将处理信号中断并清理后台进程。它使用最上面的答案中的$BASHPID
和exec
技巧获取进程的PID(在本例中为调用)。它使用FIFO与负责终止和清理的子Shell通信。(这就像我第二个答案中的管道一样,但是拥有一个命名管道意味着信号处理程序也可以写入其中。)$$
sh
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
因为它试图杀死一个已经退出的进程。