Jak wprowadzić limit czasu dla skryptów powłoki?

35

Chcę uruchomić skrypt powłoki, który ma w sobie pętlę i może trwać wiecznie, czego nie chcę. Więc muszę wprowadzić limit czasu dla całego skryptu.

Jak mogę wprowadzić limit czasu dla całego skryptu powłoki w SuSE?

Radek
źródło
4
To bardzo niejasne pytanie. Czy możesz wyjaśnić, jaki rodzaj limitu czasu? Czy chcesz przekroczyć limit czasu dla całego skryptu, czy limit czasu dla pojedynczej funkcji lub polecenia? Co próbujesz zrobić?
Tim Kennedy,
@TimKennedy: mam nadzieję, że teraz jest lepiej.
Radek
1
Zobacz także Przekroczanie limitu czasu w skrypcie powłoki (nie uważam tego za duplikat ze względu na mój wymóg przenośności, który wyklucza narzędzia takie jak timeoutlub expect)
Gilles „SO- przestań być zły”

Odpowiedzi:

30

Jeśli GNU timeoutnie jest dostępne, możesz użyć expect(Mac OS X, BSD, ... zwykle nie mają domyślnie narzędzi i narzędzi GNU).

################################################################################
# Executes command with a timeout
# Params:
#   $1 timeout in seconds
#   $2 command
# Returns 1 if timed out 0 otherwise
timeout() {

    time=$1

    # start the command in a subshell to avoid problem with pipes
    # (spawn accepts one command)
    command="/bin/sh -c \"$2\""

    expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }"    

    if [ $? = 1 ] ; then
        echo "Timeout after ${time} seconds"
    fi

}

Edytuj przykład:

timeout 10 "ls ${HOME}"
Matteo
źródło
Wygląda dobrze. Jak mogę zobaczyć wynik polecenia wykonanego na ekranie?
Radek
Cześć, wynik powinien być widoczny, ponieważ standardowe wyjście nie jest nigdzie przekierowywane. Dodałem przykład użycia. W moim przypadku drukuje zawartość mojego domowego katalogu zgodnie z oczekiwaniami. Które polecenie nie wyświetla żadnego wyniku?
Matteo,
Nie nazwałam tego poprawnie. Działa po prostu świetnie! Dziękuję Ci. Tylko, że nie wyświetla liczby sekund po upływie limitu czasu.
Radek
@Radek Przepraszamy, naprawiono
Matteo,
1
Chciałbym podkreślić, że jeśli używasz limitu czasu GNU, w przypadku przekroczenia limitu czasu sam jego zwrot timeoutwynosi 124, w przeciwnym razie jest to status powrotu polecenia (jest to wspomniane w odpowiedzi Tima Kennedy'ego).
QuasarDonkey,
15

Dziękuję za wyjaśnienie.

Najłatwiejszym sposobem na osiągnięcie tego, czego szukasz, jest uruchomienie skryptu za pomocą pętli w opakowaniu, takiej jak timeoutpolecenie z pakietu GNU Coreutils.

root@coraid-sp:~# 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.
  -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 an integer with an optional suffix:
`s' for seconds(the default), `m' for minutes, `h' for hours or `d' for days.

If the command times out, 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.  For other processes, it may be necessary to
use the KILL (9) signal, since this signal cannot be caught.

Report timeout bugs to [email protected]
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'timeout invocation'

W końcu będzie to o wiele łatwiejsze niż napisanie własnej funkcji limitu czasu, której powłoki zwykle nie mają wbudowanego.

Tim Kennedy
źródło
Nie zdawałem sobie sprawy, że mam to zainstalowane, ponieważ jest ono wywoływane gtimeoutw mojej konfiguracji (OSX).
Joshua Goldberg
7

Uruchom proces nadzoru z poziomu skryptu, aby zabić jego rodzica, jeśli działa zbyt długo. Przykład:

# watchdog process
mainpid=$$
(sleep 5; kill $mainpid) &
watchdogpid=$!

# rest of script
while :
do
   ...stuff...
done
kill $watchdogpid

Ten skrypt zostanie zakończony przez strażnika po pięciu sekundach.

Kyle Jones
źródło
6
Ale za każdym razem będziesz musiał czekać na limit czasu. W twoim przykładzie twój skrypt będzie zawsze działał 5 sekund, nawet jeśli skrypt jest znacznie krótszy. Powinieneś również zapisać PID organu nadzorującego i zabić go na końcu.
Matteo,
@Matteo Do przyjęcia. Dodany.
Kyle Jones
1
Pamiętaj, że może to być bardziej skomplikowane, w zależności od tego, co ... robią ... rzeczy. Zobacz Limit czasu w skrypcie powłoki
Gilles „SO- przestań być zły”
czytaj także: jak długo można uznać PID za unikalne stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane
3

Jest też cratimeoutMartin Cracauer.

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
jackz
źródło
2
#!/bin/sh

# Execute a command with a timeout

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
Vivek Ragupathy
źródło