如何限制 BASH 脚本的运行时间
Posted
技术标签:
【中文标题】如何限制 BASH 脚本的运行时间【英文标题】:How do I limit the running time of a BASH script 【发布时间】:2010-10-06 07:43:16 【问题描述】:我有一个长时间运行的 BASH 脚本,我在 Windows 上的 CYGWIN 下运行。
我想限制脚本运行 30 秒,如果超过此限制,则自动终止。理想情况下,我希望能够对任何命令执行此操作。
例如:
sh-3.2$ limittime -t 30 'myscript.sh'
或
sh-3.2$ limittime -t 30 'grep func *.c'
在 cygwin 下 ulimit 命令似乎不起作用。
我愿意接受任何想法。
【问题讨论】:
@jm,请参阅我的更新答案,了解如何在最早的超时和子退出正常情况下停止等待。 类似的问题,一些不同的答案:***.com/questions/687948 有什么理由不使用 gnutimeout
实用程序?
timeout
太棒了!您甚至可以使用 多个命令(多行脚本):***.com/a/61888916/658497
【参考方案1】:
查看http://www.pixelbeat.org/scripts/timeout 脚本,其功能已集成到较新的 coreutils 中:
#!/bin/sh
# Execute a command with a timeout
# License: LGPLv2
# Author:
# http://www.pixelbeat.org/
# Notes:
# Note there is a timeout command packaged with coreutils since v7.0
# If the timeout occurs the exit status is 124.
# There is an asynchronous (and buggy) equivalent of this
# script packaged with bash (under /usr/share/doc/ in my distro),
# which I only noticed after writing this.
# I noticed later again that there is a C equivalent of this packaged
# with satan by Wietse Venema, and copied to forensics by Dan Farmer.
# Changes:
# V1.0, Nov 3 2006, Initial release
# V1.1, Nov 20 2007, Brad Greenlee <brad@footle.org>
# Make more portable by using the 'CHLD'
# signal spec rather than 17.
# V1.3, Oct 29 2009, Ján Sáreník <jasan@x31.com>
# Even though this runs under dash,ksh etc.
# it doesn't actually timeout. So enforce bash for now.
# Also change exit on timeout from 128 to 124
# to match coreutils.
# V2.0, Oct 30 2009, Ján Sáreník <jasan@x31.com>
# Rewritten to cover compatibility with other
# Bourne shell implementations (pdksh, dash)
if [ "$#" -lt "2" ]; then
echo "Usage: `basename $0` timeout_in_seconds command" >&2
echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
exit 1
fi
cleanup()
trap - ALRM #reset handler to default
kill -ALRM $a 2>/dev/null #stop timer subshell if running
kill $! 2>/dev/null && #kill last job
exit 124 #exit with 124 if it was running
watchit()
trap "cleanup" ALRM
sleep $1& wait
kill -ALRM $$
watchit $1& a=$! #start the timeout
shift #first param was timeout for sleep
trap "cleanup" ALRM INT #cleanup after timeout
"$@"& wait $!; RET=$? #start the job wait for it and save its return value
kill -ALRM $a #send ALRM signal to watchit
wait $a #wait for watchit to finish cleanup
exit $RET #return the value
【讨论】:
这是一个不错的脚本。在 CYGWIN 下也能正常工作。 如果您使用标准输入重定向,此脚本将不起作用 - 类似:./time_limit.sh cat 【参考方案2】:以下脚本显示了如何使用后台任务执行此操作。第一部分在 10 秒限制后终止了一个 60 秒的进程。第二次尝试杀死已经退出的进程。请记住,如果您将超时设置得非常高,则进程 ID 可能会翻转并且您会终止错误的进程,但这更多是理论上的问题 - 超时必须非常很大,您将不得不开始很多个进程。
#!/usr/bin/bash
sleep 60 &
pid=$!
sleep 10
kill -9 $pid
sleep 3 &
pid=$!
sleep 10
kill -9 $pid
这是我的 Cygwin 机器上的输出:
$ ./limit10
./limit10: line 9: 4492 Killed sleep 60
./limit10: line 11: kill: (4560) - No such process
如果您只想等到该过程完成,您需要进入一个循环并检查。由于sleep 1
和其他命令实际上将花费超过一秒(但不会更多),因此这稍微不太准确。使用此脚本替换上面的第二部分(“echo $proc
”和“date
”命令用于调试,我不希望在最终解决方案中包含它们)。
#!/usr/bin/bash
date
sleep 3 &
pid=$!
((lim = 10))
while [[ $lim -gt 0 ]] ; do
sleep 1
proc=$(ps -ef | awk -v pid=$pid '$2==pidprint')
echo $proc
((lim = lim - 1))
if [[ -z "$proc" ]] ; then
((lim = -9))
fi
done
date
if [[ $lim -gt -9 ]] ; then
kill -9 $pid
fi
date
它基本上是循环的,每秒检查进程是否仍在运行。如果不是,它会以特殊值退出循环,以不尝试杀死孩子。否则它会超时并杀死孩子。
这是sleep 3
的输出:
Mon Feb 9 11:10:37 WADT 2009
pax 4268 2476 con 11:10:37 /usr/bin/sleep
pax 4268 2476 con 11:10:37 /usr/bin/sleep
Mon Feb 9 11:10:41 WADT 2009
Mon Feb 9 11:10:41 WADT 2009
还有一个sleep 60
:
Mon Feb 9 11:11:51 WADT 2009
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
pax 4176 2600 con 11:11:51 /usr/bin/sleep
Mon Feb 9 11:12:03 WADT 2009
Mon Feb 9 11:12:03 WADT 2009
./limit10: line 20: 4176 Killed sleep 60
【讨论】:
这很好,但是进程可以运行的最小长度现在是超时。不过,这对于我提出问题的方式是正确的。 确认,除非绝对必要,否则不要使用 kill -9! SIGKILL 不能被捕获,因此被杀死的程序不能运行任何关闭例程,例如擦除临时文件。首先尝试 HUP (1),然后是 INT (2),然后是 QUIT (3)。 信号是一个样本,@www。如果您了解底层脚本在做什么,那么使用 -9 是完全可以接受的。否则,您必须使用 HUP、wait、INT、wait、QUIT 等使代码复杂化。不好回答,但你是对的,现实世界中可能需要它。 此方法需要限时脚本在后台运行,这可能是不可取的。它还具有 'ps' 和 'kill' 之间的竞争条件。我喜欢 bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/…> 在前台运行,并在 KILL 之前发送 TERM。 @www.blindrut.ca~neitsch,HUP 对不同的程序是否意味着不同的东西,例如。 “重新读取您的配置文件”?而且我怀疑 TERM 是比 INT 更优雅的关闭。 ***.com/questions/690415/…【参考方案3】:查看this link。这个想法只是你将 myscript.sh
作为脚本的子进程运行并记录其 PID,然后在运行时间过长时将其终止。
【讨论】:
我无法让该示例在 cygwin 上运行。我得到了:sh:第 46 行:kill:SIGUSR1:无效的信号规范 这个解决方案实际上看起来很奇怪。它启动定时任务,然后是一个单独的子 shell 以休眠并将 USR1 发送到正在运行的 shell。为什么不直接在运行的 shell 中睡觉呢? 就像 Pax 接受的答案一样,该脚本要求您的脚本在后台运行,并且在“ps”和“kill”之间存在竞争条件。当然,比赛条件严格来说只是表面上的东西。【参考方案4】:您可以将命令作为后台作业运行(即使用“&”),将 bash 变量用于“最后一次命令运行的 pid”,休眠必要的时间,然后使用该 pid 运行 kill
。
【讨论】:
【参考方案5】:timeout 30s YOUR_COMMAND COMMAND_ARGUMENTS
以下是coreutils下“超时”的所有选项:
$ timeout --help
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
or: timeout [OPTION]
Start COMMAND, and kill it if still running after DURATION.
Mandatory arguments to long options are mandatory for short options too.
--preserve-status
exit with the same status as COMMAND, even when the
command times out
--foreground
when not running timeout directly from a shell prompt,
allow COMMAND to read from the TTY and get TTY signals;
in this mode, children of COMMAND will not be timed out
-k, --kill-after=DURATION
also send a KILL signal if COMMAND is still running
this long after the initial signal was sent
-s, --signal=SIGNAL
specify the signal to be sent on timeout;
SIGNAL may be a name like 'HUP' or a number;
see 'kill -l' for a list of signals
--help display this help and exit
--version output version information and exit
DURATION is a floating point number with an optional suffix:
's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days.
If the command times out, and --preserve-status is not set, then exit with
status 124. Otherwise, exit with the status of COMMAND. If no signal
is specified, send the TERM signal upon timeout. The TERM signal kills
any process that does not block or catch that signal. It may be necessary
to use the KILL (9) signal, since this signal cannot be caught, in which
case the exit status is 128+9 rather than 124.
GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
Full documentation at: <http://www.gnu.org/software/coreutils/timeout>
or available locally via: info '(coreutils) timeout invocation'
【讨论】:
以上是关于如何限制 BASH 脚本的运行时间的主要内容,如果未能解决你的问题,请参考以下文章
如何在路径有空格的另一个 bash 脚本中运行 bash 脚本?