Wie führe ich ein Timeout für Shell-Skripte ein?

35

Ich möchte ein Shell-Skript ausführen, das eine Schleife enthält und das für immer funktionieren kann, was ich nicht möchte. Daher muss ich eine Zeitüberschreitung für das gesamte Skript festlegen.

Wie kann ich unter SuSE ein Timeout für das gesamte Shell-Skript einführen?

Radek
quelle
4
Dies ist eine sehr vage Frage. Können Sie bitte erläutern, auf welche Art von Zeitüberschreitung? Möchten Sie eine Zeitüberschreitung für das gesamte Skript oder eine Zeitüberschreitung für eine einzelne Funktion oder einen einzelnen Befehl? Was versuchst du zu machen?
Tim Kennedy
@ TimKennedy: Hoffe, es ist jetzt besser.
Radek
1
Siehe auch Zeitüberschreitung in einem Shell-Skript (ich betrachte es aufgrund meiner Portabilitätsanforderung, die Tools wie timeoutoder ausschließt, nicht als Duplikat expect)
Gilles 'SO - hör auf, böse zu sein'

Antworten:

30

Wenn GNU timeoutnicht verfügbar ist, können Sie es verwenden expect(Mac OS X, BSD, ... verfügen normalerweise nicht über GNU-Tools und -Dienstprogramme).

################################################################################
# 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

}

Beispiel bearbeiten :

timeout 10 "ls ${HOME}"
Matteo
quelle
Sieht gut aus. Wie kann ich die Ausgabe des ausgeführten Befehls auf dem Bildschirm sehen?
Radek
Hallo, die Ausgabe sollte sichtbar sein, da stdout nirgends umgeleitet wird. Ich habe ein Beispiel für die Verwendung hinzugefügt. In meinem Fall wird der Inhalt meines Heimatverzeichnisses wie erwartet gedruckt. Welcher Befehl druckt keine Ausgabe?
Matteo
Ich habe es nicht richtig genannt. Es funktioniert einfach super! Vielen Dank. Es wird nur keine Anzahl von Sekunden nach dem Timeout ausgegeben.
Radek,
@ Radek Sorry, behoben
Matteo
1
Ich möchte hervorheben, dass bei Verwendung des GNU-Timeouts der Rückgabestatus für sich timeoutselbst im Falle eines Timeouts 124 ist, andernfalls ist es der Rückgabestatus des Befehls (dies wird in der Antwort von Tim Kennedy erwähnt).
QuasarDonkey
15

Danke für die Klarstellung.

Der einfachste Weg, das zu erreichen, wonach Sie suchen, besteht darin, Ihr Skript mit der Schleife in einem Wrapper wie dem timeoutBefehl aus dem GNU Coreutils-Paket auszuführen.

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'

Am Ende wird es viel einfacher sein, als eine eigene Timeout-Funktion zu schreiben, bei der Shells in der Regel nicht eingebaut sind.

Tim Kennedy
quelle
Ich habe nicht sofort gemerkt, dass ich das installiert habe, weil es gtimeoutauf meinem Setup (OSX) aufgerufen wird .
Joshua Goldberg
7

Starten Sie einen Watchdog-Prozess in Ihrem Skript, um den übergeordneten Prozess abzubrechen, wenn er zu lange ausgeführt wird. Beispiel:

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

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

Dieses Skript wird nach fünf Sekunden vom Watchdog beendet.

Kyle Jones
quelle
6
Aber Sie müssen jedes Mal auf das Timeout warten. In Ihrem Beispiel wird Ihr Skript immer 5 Sekunden ausgeführt, auch wenn das Skript viel kürzer ist. Sie sollten auch die PID des Watchdogs speichern und am Ende töten.
Matteo
@ Matteo Fair genug. Hinzugefügt.
Kyle Jones
1
Beachten Sie, dass es komplizierter sein kann, je nachdem, was das ... Zeug ... tut. Siehe Zeitüberschreitung in einem Shell-Skript
Gilles 'SO - hör auf, böse zu sein'
Lesen Sie auch: Wie lange können PIDs als einzigartig angesehen werden ? stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane
3

Es gibt auch cratimeoutvon Martin 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
quelle
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
quelle