Как ограничить время выполнения скрипта BASH
У меня есть долго работающий BASH-скрипт, который я запускаю под CYGWIN в Windows.
Я хотел бы ограничить выполнение сценария в течение 30 секунд и автоматически завершать работу, если он превышает этот предел. В идеале я хотел бы иметь возможность сделать это с любой командой.
Например:
sh-3.2$ limittime -t 30 'myscript.sh'
или же
sh-3.2$ limittime -t 30 'grep func *.c'
Под cygwin команда ulimit не работает.
Я открыт для любых идей.
4 ответа
См. 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
Следующий скрипт показывает, как это сделать с помощью фоновых задач. Первый раздел убивает 60-секундный процесс после 10-секундного ограничения. Вторая попытка убить процесс, который уже завершен. Имейте в виду, что если вы установите очень большое время ожидания, идентификаторы процессов могут перевернуться, и вы убьете неправильный процесс, но это скорее теоретическая проблема - время ожидания должно быть очень большим, и вы должны быть начиная много процессов.
#!/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==pid{print}{}')
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
Проверьте эту ссылку. Идея в том, что вы бы myscript.sh
как подпроцесс вашего скрипта и запишите его PID, а затем убейте его, если он выполняется слишком долго.
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 для "pid of last run run"," sleep" в течение необходимого количества времени, а затем запустить kill
с этим пид.