Как ограничить время выполнения скрипта BASH - PullRequest
16 голосов
/ 09 февраля 2009

У меня есть долго работающий BASH-скрипт, который я запускаю под CYGWIN в Windows.

Я бы хотел ограничить выполнение скрипта 30 секундами и автоматически завершать работу, если он превышает это ограничение В идеале я хотел бы иметь возможность сделать это с любой командой.

Например:

sh-3.2$ limittime -t 30 'myscript.sh'

или

sh-3.2$ limittime -t 30 'grep func *.c'

Под cygwin команда ulimit, похоже, не работает.

Я открыт для любых идей.

Ответы [ 4 ]

14 голосов
/ 02 февраля 2010

См. Скрипт 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
12 голосов
/ 09 февраля 2009

Следующий скрипт показывает, как сделать это с помощью фоновых задач. Первый раздел убивает 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
5 голосов
/ 09 февраля 2009

Проверить эту ссылку . Идея в том, что вы просто запустите myscript.sh в качестве подпроцесса вашего скрипта и запишите его PID, а затем убьете его, если он выполняется слишком долго.

2 голосов
/ 09 февраля 2009

Вы можете запустить команду как фоновое задание (т. Е. С «&»), использовать переменную bash для «pid of last run run», «sleep» в течение необходимого количества времени, а затем запустить kill с этим pid.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...