372 Stimmen

Timeout eines Befehls in der Bash ohne unnötige Verzögerung

Diese Antwort まで Kommandozeilenbefehl zum automatischen Beenden eines Befehls nach einer bestimmten Zeitspanne

schlägt eine 1-Zeilen-Methode vor, um einen lang laufenden Befehl in der Bash-Kommandozeile zu unterbrechen:

( /path/to/slow command with options ) & sleep 5 ; kill $!

Es ist jedoch möglich, dass ein bestimmter "langlaufender" Befehl früher endet als die Zeitüberschreitung.
(Nennen wir es einen "typisch-lang-laufenden-aber-manchmal-schnellen" Befehl, oder tlrbsf zum Spaß).

Dieser elegante Einzeiler hat also ein paar Probleme.
Erstens, die sleep ist nicht an Bedingungen geknüpft, was zu einer unerwünschten Untergrenze der Zeit führt, die für die Beendigung der Sequenz benötigt wird. Ziehen Sie 30s oder 2m oder sogar 5m für den Schlaf in Betracht, wenn die tlrbsf Befehl ist in 2 Sekunden beendet - höchst unerwünscht.
Zweitens, die kill ist nicht an Bedingungen geknüpft, so dass diese Sequenz versuchen wird, einen nicht laufenden Prozess zu beenden und sich darüber beschweren wird.

Also...

Gibt es eine Möglichkeit um eine typischerweise lange laufende, aber manchmal schnelle ( "tlrbsf" ) den Befehl

  • hat eine Bash-Implementierung (die andere Frage wurde bereits mit Perl und C beantwortet)
  • endet mit dem früheren der beiden Zeitpunkte: tlrbsf Programmabbruch oder Timeout verstrichen
  • wird nicht existierende/nicht laufende Prozesse nicht beenden (oder, optional: wird nicht beschweren über eine schlechte Tötung)
  • muss kein Einzeiler sein
  • kann unter Cygwin oder Linux laufen

... und, für Bonuspunkte

  • läuft die tlrbsf Befehl im Vordergrund
  • jeder "Ruhezustand" oder zusätzliche Prozess im Hintergrund

so, dass die stdin/stdout/stderr des tlrbsf Befehl umgeleitet werden kann, so als ob er direkt ausgeführt worden wäre?

Wenn ja, teilen Sie bitte Ihren Code mit. Wenn nicht, erklären Sie bitte warum.

Ich habe eine Weile versucht, das oben genannte Beispiel zu hacken, aber ich stoße an die Grenzen meiner Bash-Kenntnisse.

668voto

yingted Punkte 9406

Sie suchen wahrscheinlich nach dem timeout Befehl in coreutils. Da es ein Teil von coreutils ist, ist es technisch gesehen eine C-Lösung, aber es ist immer noch coreutils. info timeout für weitere Einzelheiten. Hier ist ein Beispiel:

timeout 5 /path/to/slow/command with options

170voto

Juliano Punkte 35709

Ich glaube, das ist genau das, worum Sie bitten:

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <Dmitry.Golovashkin@sas.com>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $$ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $$ && kill -0 $$ || exit 0
    sleep $delay
    kill -s SIGKILL $$
) 2> /dev/null &

exec "$@"

47voto

Dmitry Punkte 431

Diese Lösung funktioniert unabhängig vom Bash-Monitor-Modus. Sie können das richtige Signal zum Beenden von your_command verwenden

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

Der Watcher beendet your_command nach einem bestimmten Timeout; das Skript wartet auf die langsame Aufgabe und beendet den Watcher. Beachten Sie, dass wait funktioniert nicht mit Prozessen, die Kinder einer anderen Shell sind.

Beispiele:

  • your_command läuft länger als 2 Sekunden und wurde abgebrochen

Ihr_Befehl unterbrochen

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • your_command wurde vor dem Timeout (20 Sekunden) beendet

Ihr_Befehl beendet

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi

35voto

lance.dolan Punkte 2702

Für die Zeitüberschreitung der slowcommand nach 1 Sekunde:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

Um festzustellen, ob der Befehl wegen Zeitüberschreitung oder aus eigenen Gründen fehlgeschlagen ist, prüfen Sie, ob der Statuscode 124 lautet:

# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

Beachten Sie, dass Sie bei einem Exit-Status von 124 nicht wissen, ob die Zeitüberschreitung durch Ihre timeout oder ob der Befehl selbst aufgrund einer eigenen internen Timeout-Logik abgebrochen und dann 124 zurückgegeben wurde. Sie können jedoch in beiden Fällen davon ausgehen, dass eine Zeitüberschreitung auftrat.

28voto

Das war's:

timeout --signal=SIGINT 10 /path/to/slow command with options

können Sie die SIGINT y 10 wie Sie wünschen ;)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X