Как ограничить время работы BASH script

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

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

Например:

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

или

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

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

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

Ответ 1

См. http://www.pixelbeat.org/scripts/timeout script, функциональность которого была интегрирована в более новые ядра:

#!/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 <[email protected]>
#                       Make more portable by using the 'CHLD'
#                       signal spec rather than 17.
#    V1.3, Oct 29 2009, Ján Sáreník <[email protected]>
#                       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 <[email protected]>
#                       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
"[email protected]"& 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

Ответ 2

Следующий script показывает, как это сделать, используя фоновые задачи. Первый раздел убивает 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 и другие команды на самом деле занимают более одной секунды (но не намного больше). Используйте этот script для замены второго раздела выше (команды "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

и a 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

Ответ 3

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

Ответ 4

Вы можете запустить команду в качестве фонового задания (т.е. с помощью "&" ), используйте переменную bash для "pid последнего запуска команды", сон в течение необходимого количества времени, затем запустите kill с помощью что pid.