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


283

Diese Antwort auf den Befehlszeilenbefehl zum automatischen Beenden eines Befehls nach einer bestimmten Zeit

schlägt eine 1-zeilige Methode vor, um einen Befehl mit langer Laufzeit über die Bash-Befehlszeile zu deaktivieren:

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

Es ist jedoch möglich, dass ein bestimmter "lang laufender" Befehl vor dem Timeout beendet wird. (Nennen wir es einen "normalerweise lang laufenden, aber manchmal schnellen" Befehl oder tlrbsf zum Spaß.)

Dieser raffinierte 1-Liner-Ansatz hat also einige Probleme. Erstens ist das sleepnicht bedingt, so dass eine unerwünschte Untergrenze für die Zeit festgelegt wird, die für den Abschluss der Sequenz benötigt wird. Betrachten Sie 30s oder 2m oder sogar 5m für den Schlaf, wenn der Befehl tlrbsf in 2 Sekunden beendet ist - höchst unerwünscht. Zweitens ist das killunbedingt erforderlich, sodass diese Sequenz versucht, einen nicht laufenden Prozess zu beenden und darüber zu jammern.

So...

Gibt es eine Möglichkeit , einen normalerweise lang laufenden, aber manchmal schnellen Befehl ( "tlrbsf" ) zu umgehen?

  • hat eine Bash-Implementierung (die andere Frage hat bereits Perl- und C-Antworten)
  • wird am früheren der beiden beendet: tlrbsf- Programmbeendigung oder abgelaufenes Timeout
  • tötet nicht vorhandene / nicht laufende Prozesse nicht ab (oder beschwert sich optional nicht über einen schlechten Kill)
  • muss kein 1-Liner sein
  • kann unter Cygwin oder Linux laufen

... und führt für Bonuspunkte den Befehl tlrbsf im Vordergrund und einen beliebigen "Schlaf" - oder zusätzlichen Prozess im Hintergrund aus, sodass der Befehl stdin / stdout / stderr des Befehls tlrbsf wie zuvor umgeleitet werden kann direkt laufen?

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

Ich habe eine Weile versucht, das oben genannte Beispiel zu hacken, aber ich stoße an die Grenze meiner Bash-Fähigkeiten.


5
Eine andere ähnliche Frage: stackoverflow.com/questions/526782/… (aber ich denke, die Antwort 'timeout3' hier ist viel besser).
System PAUSE

2
Gibt es einen Grund, das timeoutDienstprogramm gnu nicht zu verwenden ?
Chris Johnson

timeoutist toll! Sie können sogar mehrere Befehle verwenden (mehrzeiliges Skript): stackoverflow.com/a/61888916/658497
Noam Manos

Antworten:


149

Ich denke, genau darum bitten Sie:

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 "$@"

Das ist ein scharfer Trick, bei dem $$ für den Hintergrundteil verwendet wird. Und schön, dass es einen hängenden tlrbsf sofort tötet. Aber ugh, du musst ein Abfrageintervall wählen. Und wenn Sie die Abfrage zu niedrig einstellen, wird die CPU mit konstanter Signalisierung aufgebraucht, wodurch der tlrbsf noch länger läuft!
System PAUSE

7
Sie müssen das Abfrageintervall nicht auswählen, es hat einen Standardwert von 1s, das ist ziemlich gut. Und die Überprüfung ist sehr kostengünstig, der Overhead ist vernachlässigbar. Ich bezweifle, dass dadurch tlrbsf merklich länger laufen würde. Ich habe mit Schlaf 30 getestet und einen Unterschied von 0,000 ms zwischen Verwenden und Nicht-Verwenden festgestellt.
Juliano

6
Richtig, das sehe ich jetzt. Und es entspricht genau meinen Anforderungen, wenn Sie das Zeitintervall für das Abfrageintervall == festlegen. Funktioniert auch in Pipelines, arbeitet mit dem gesamten Hintergrund, funktioniert mit mehreren Instanzen und anderen ausgeführten Jobs. Süß, danke!
System PAUSE

Das Senden eines Signals tötet die Sub-Shell, daher dachte ich, dass alle Kill-Befehle in einer Zeile erhalten bleiben. Ich habe auch die stderr-Ausgabe aktiviert, um unerwartete Fehler anzuzeigen. stackoverflow.com/questions/687948/…
Aal GhEEz

1
@ Juliano Das ist eine großartige Möglichkeit, mit Zeitüberschreitungen umzugehen, sehr nützlich. Ich frage mich, ob es eine Möglichkeit gibt, wie das Skript den Exitcode 143 zurückgeben kann, wenn der Prozess nach einer Zeitüberschreitung abgebrochen wird. Ich habe versucht, "exit 143" direkt nach dem Befehl kill hinzuzufügen, erhalte jedoch immer den Exit-Code 0 im Aufruferskript.
Salman A. Kagzi

528

Sie suchen wahrscheinlich nach dem timeoutBefehl in coreutils. Da es ein Teil von Coreutils ist, ist es technisch gesehen eine C-Lösung, aber es sind immer noch Coreutils. info timeoutfür mehr Details. Hier ist ein Beispiel:

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

21
In Mac können Sie dies über Macports oder Homebrew installieren.
Ivan Z. Siu

23
Bei der Installation über Homebrew unter OS X wird der Befehlgtimeout
ethischhack3r

5
... welches Betriebssystem verwenden Sie, das Coreutils aus der Zeit vor 2003 enthält?
Keith

5
@ Keith: CentOS 5.10, zum Beispiel :-(
Bis auf weiteres angehalten.

9
Zur Verdeutlichung lautet der Homebrew-Befehl, den Sie unter OSX / Mac benötigen brew install coreutils, und Sie können ihn dann verwenden gtimeout.
Ohad Schneider

37

Diese Lösung funktioniert unabhängig vom Bash-Monitor-Modus. Sie können das richtige Signal verwenden, um Ihren Befehl zu beenden

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

Der Beobachter beendet Ihren Befehl nach einer bestimmten Zeitüberschreitung. Das Skript wartet auf die langsame Aufgabe und beendet den Watcher. Beachten Sie, dass waitdies nicht mit Prozessen funktioniert, die untergeordnete Elemente einer anderen Shell sind.

Beispiele:

  • Ihr_Befehl läuft länger als 2 Sekunden und wurde beendet

Ihr Befehl wurde 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
  • Ihr_Befehl wurde vor dem Timeout beendet (20 Sekunden)

Ihr Befehl ist 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

1
waitGibt den Exit-Status des Prozesses zurück, auf den gewartet wird. Wenn Ihr Befehl also innerhalb der zugewiesenen Zeit, jedoch mit einem Exit-Status ungleich Null, beendet wird, verhält sich die Logik hier so, wie sie abgelaufen ist, dh gedruckt your_command interrupted. Stattdessen können Sie auf das waitverzichten ifund dann überprüfen, ob die $watcherPID noch vorhanden ist. Wenn dies der Fall ist, wissen Sie, dass Sie kein Timeout durchgeführt haben.
George Hawkins

Ich kann nicht herausfinden, warum dies killin einem Fall verwendet wird, aber pkillin dem anderen. Ich musste pkillfür beide verwenden, damit dies richtig funktioniert. Ich würde erwarten, dass Sie einen Befehl ()verwenden müssen pkill, um ihn zu beenden, wenn Sie ihn einschließen . Aber vielleicht funktioniert es anders, wenn es nur einen einzigen Befehl innerhalb der gibt ().
Nobar

Gott segne Sie, Sir :)
Darko Miletic

22

Los geht's:

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

du kannst das SIGINTund 10wie du willst ändern ;)


3
"timeout" ist Teil des Coreutils- Pakets auf (mindestens) Redhat, Centos, Suse und Ubuntu. Sie müssen es also installieren, wenn Sie es nicht haben.
Akom

es ist wirklich hilfreich !!!!!! Wissen Sie, warum Yingteds "Timeout 5 / Pfad / zu / langsam / Befehl mit Optionen" manchmal nicht funktioniert?
Decula

Leider ist dies nicht im coreutilsPaket auf FreeBSD enthalten.
Paul Bissex

18

Sie können dies vollständig mit bash 4.3und über tun :

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Beispiel: _timeout 5 longrunning_command args
  • Beispiel: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • Beispiel: producer | { _timeout 5 consumer1; consumer2; }
  • Beispiel: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Benötigt Bash 4.3 für wait -n

  • Gibt 137 an, wenn der Befehl beendet wurde, andernfalls den Rückgabewert des Befehls.
  • Funktioniert für Rohre. (Sie müssen hier nicht in den Vordergrund gehen!)
  • Funktioniert auch mit internen Shell-Befehlen oder -Funktionen.
  • Läuft in einer Subshell, daher kein Variablenexport in die aktuelle Shell, sorry.

Wenn Sie den Rückkehrcode nicht benötigen, kann dies noch einfacher gemacht werden:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Anmerkungen:

  • Genau genommen brauchen Sie das ;In nicht ; ), aber es macht die Sache konsistenter mit dem ; }Fall. Und das set +bkann man wahrscheinlich auch weglassen, aber besser sicher als leid.

  • Mit Ausnahme --forground(wahrscheinlich) können Sie alle Varianten timeoutunterstützen. --preserve-statusist allerdings etwas schwierig. Dies bleibt als Übung für den Leser;)

Dieses Rezept kann "natürlich" in der Schale verwendet werden (so natürlich wie für flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

Wie oben erläutert, können Sie Umgebungsvariablen auf diese Weise natürlich nicht erneut in die umschließende Shell exportieren.

Bearbeiten:

Beispiel aus der __git_ps1Praxis : Auszeit für den Fall, dass es zu lange dauert (für Dinge wie langsame SSHFS-Links):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2: Bugfix. Mir ist aufgefallen, dass exit 137das nicht benötigt wird und gleichzeitig _timeoutunzuverlässig macht .

Edit3: gitist ein eingefleischter, daher braucht es einen doppelten Trick, um zufriedenstellend zu arbeiten.

Edit4: Ich habe ein _erstes _timeoutGIT-Beispiel für die reale Welt vergessen .


1
Bash 4 Felsen. Das ist alles.
System PAUSE

1
Dies erfordert tatsächlich Bash 4.3 oder neuer. cc. The 'wait' builtin has a new '-n' option to wait for the next child to change status. Von: tiswww.case.edu/php/chet/bash/NEWS
Ben Reser

17

Ich bevorzuge "timelimit", das ein Paket zumindest in Debian hat.

http://devel.ringlet.net/sysutils/timelimit/

Es ist ein bisschen schöner als das "Timeout" der Coreutils, da es beim Beenden des Prozesses etwas druckt und nach einiger Zeit standardmäßig auch SIGKILL sendet.


Es scheint nicht ganz gut zu funktionieren: / $ time timelimit -T2 sleep 10 real 0m10.003s user 0m0.000s sys 0m0.000s
hithwen

3
Verwenden Sie -t2 nicht -T2. Das große -T ist die Zeit vom Senden von SIGTERM bis zum Senden von SIGKILL.
Maxy

1
Ich möchte hinzufügen, dass Timelimit 1.8 nicht gut mit der Gabel funktioniert ( timelimit -t1 ./a_forking_prognur einen der beiden Prozesse beenden ), aber Timeout funktioniert.
Jeremy Cochoy

Wenn Sie möchten, dass beim Beenden des Vorgangs eine Zeitüberschreitung auftritt, verwenden Sie einfach das Flag "-v".

10

Zeitüberschreitung slowcommandnach 1 Sekunde:

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

Überprüfen Sie, ob der Statuscode 124 lautet, um festzustellen, ob der Befehl aus eigenen Gründen abgelaufen ist oder fehlgeschlagen ist:

# ping 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 timeout, ob das Zeitlimit aufgrund Ihres Befehls abgelaufen ist oder ob der Befehl selbst aufgrund einer eigenen internen Zeitüberschreitungslogik beendet und dann zurückgegeben wurde 124. Sie können in beiden Fällen sicher davon ausgehen Es kam jedoch zu einer Auszeit.



8

Ein bisschen hacky, aber es funktioniert. Funktioniert nicht, wenn Sie andere Vordergrundprozesse haben (bitte helfen Sie mir, dies zu beheben!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

Eigentlich denke ich, dass Sie es umkehren können, indem Sie Ihre Bonuskriterien erfüllen:

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa

(ls -ltR / cygdrive / c / windows & SPID = $ {!}; (Schlaf 1s; töte $ {SPID}) & CPID = $ {!}; fg 1; töte $ {CPID})> fdsa
System PAUSE

bash: fg: keine
Jobkontrolle

@system PAUSE, setze -m, denke ich.
Strager

Ich habe Jobsteuerung (set -m) in der Login-Shell. Das ist das 'm' im himBH-Inhalt von $ -, aber es scheint für die Subshells zu verschwinden. Möglicherweise ein Cygwin-Artefakt. murren
System PAUSE

Verwenden Sie "fg" nicht in Skripten. Lesen Sie "Hilfe warten".
lhunath

8

Timeout ist wahrscheinlich der erste Ansatz. Möglicherweise müssen Sie eine Benachrichtigung oder einen anderen Befehl ausführen, wenn das Zeitlimit überschritten wird. Nach einigem Suchen und Experimentieren habe ich mir dieses Bash- Skript ausgedacht :

if 
    timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
    timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
    echo 'OK'; #if you want a positive response
else
    echo 'Not OK';
    AND_ALTERNATIVE_COMMANDS
fi

5

Einfaches Skript mit Codeklarheit. Speichern unter /usr/local/bin/run:

#!/bin/bash

# run
# Run command with timeout $1 seconds.

# Timeout seconds
timeout_seconds="$1"
shift

# PID
pid=$$

# Start timeout
(
  sleep "$timeout_seconds"
  echo "Timed out after $timeout_seconds seconds"
  kill -- -$pid &>/dev/null
) &
timeout_pid=$!

# Run
"$@"

# Stop timeout
kill $timeout_pid &>/dev/null

Zeitüberschreitung eines zu langen Befehls:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

Beendet sofort für einen Befehl, der ausgeführt wird:

$ run 10 sleep 2
$

3

Wenn Sie bereits den Namen des Programms kennen (nehmen wir an program), das nach dem Timeout beendet werden soll (als Beispiel 3Sekunden), kann ich eine einfache und etwas schmutzige alternative Lösung beisteuern:

(sleep 3 && killall program) & ./program

Dies funktioniert perfekt, wenn ich Benchmark-Prozesse mit Systemaufrufen aufrufe.


2
Dies beendet andere Prozesse, die zufällig den Namen verwenden, und beendet den Prozess nicht mit dem angegebenen Namen, wenn er seinen Namen ändert (z. B. durch Schreiben in argv[0], möglicherweise mit anderen Hacks, um mehr Platz zu schaffen).
Jed

Ich fand eine Variante davon praktisch, als ich versuchte, Docker-Container nach einer bestimmten Zeit anzuhalten. Der Docker-Client schien TERM / INT / KILL nicht so zu akzeptieren, dass der Container, der im Vordergrund ausgeführt wurde, tatsächlich gestoppt wurde. Es hat also (sleep 3 && docker stop <container>) &gut funktioniert , dem Container einen Namen zu geben und ihn zu verwenden . Vielen Dank!
Mat Schaffer

Ja, es ist schmutzig und begrenzt, kann aber wie { sleep 5 && kill -9 $(ps -fe | grep "program" | grep $$ | tr -s " " | cut -d" " -f2); } & SLEEPPID=$!; bash -c "program" && kill -9 $SLEEPPID"folgt verbessert werden: Auf diese Weise werden nur Jobs in der aktuellen Shell beendet.
Ton

2

Es gibt auch cratimeoutvon Martin Cracauer (geschrieben in C für Unix- und Linux-Systeme).

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'

"Insbesondere bleibt das Signalverhalten erhalten." Schön, diese Option zu haben!
System PAUSE

1

OS X verwendet Bash 4 noch nicht und verfügt auch nicht über / usr / bin / timeout. Daher ist hier eine Funktion, die unter OS X ohne Home-Brew oder Macports funktioniert, die / usr / bin / timeout (basierend auf Tino's) ähnelt Antworten). Die Validierung, Hilfe, Verwendung und Unterstützung von Parametern für andere Signale ist eine Übung für den Leser.

# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
    set -m +b
    sleep "$1" &
    SPID=${!}
    ("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
    CPID=${!}
    wait %1
    SLEEPRETVAL=$?
    if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
      RETVAL=124
      # When you need to make sure it dies
      #(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
      wait %2
    else
      wait %2
      RETVAL=$?
    fi
    return $RETVAL
) }

0

In 99% der Fälle besteht die Antwort darin, KEINE Timeout-Logik zu implementieren. Timeout-Logik ist in fast jeder Situation ein rotes Warnsignal dafür, dass etwas anderes nicht stimmt und stattdessen behoben werden sollte .

Hängt oder bricht Ihr Prozess manchmal nach n Sekunden? Dann finden Sie heraus warum und beheben Sie das stattdessen.

Abgesehen davon, um die Lösung von strager richtig zu machen, müssen Sie wait "$ SPID" anstelle von fg 1 verwenden, da Sie in Skripten keine Jobsteuerung haben (und der Versuch, sie einzuschalten, dumm ist). Darüber hinaus beruht fg 1 auf der Tatsache, dass Sie zuvor keine anderen Jobs im Skript gestartet haben, was eine schlechte Annahme ist.


4
Mit dem Zugriff auf 100% der Quelle (und der meisten Hardware, wie z. B. Netzwerk-Switches) würde ich zustimmen, dass es wahrscheinlich bessere Lösungen als eine Zeitüberschreitung gibt. Aber wenn ein 'tlrbsf' nur binär als Closed-Source-Quelle ist, müssen Sie diese Einschränkung manchmal umgehen.
System PAUSE

@lhunath, "in Skripten haben Sie keine Jobsteuerung (und der Versuch, sie einzuschalten, ist dumm)" - Bitte klären Sie hier: stackoverflow.com/questions/690266/…
System PAUSE

@system PAUSE: Antwort stackoverflow.com/questions/690266/… ist korrekt, ich habe es auch kommentiert.
lhunath

29
lhunath, was du sagst macht keinen Sinn. Es gibt unzählige Fälle, in denen eine Zeitüberschreitung eine gute Option ist, z. B. immer dann, wenn Sie über das Netzwerk gehen müssen.
Nate Murray

Eine Ausnahme: Sie schreiben Testskripte, um zu überprüfen, ob eine bereits ausgeführte Software kein Timeout-Problem hat.
Peterh - Wiedereinsetzung Monica

0

Mir wurde ein Problem beim Beibehalten des Shell-Kontexts und beim Zulassen von Zeitüberschreitungen angezeigt. Das einzige Problem dabei ist, dass die Skriptausführung bei der Zeitüberschreitung gestoppt wird - aber es entspricht den Anforderungen, die mir gestellt wurden:

#!/usr/bin/env bash

safe_kill()
{
  ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}

my_timeout()
{
  typeset _my_timeout _waiter_pid _return
  _my_timeout=$1
  echo "Timeout($_my_timeout) running: $*"
  shift
  (
    trap "return 0" USR1
    sleep $_my_timeout
    echo "Timeout($_my_timeout) reached for: $*"
    safe_kill $$
  ) &
  _waiter_pid=$!
  "$@" || _return=$?
  safe_kill $_waiter_pid -USR1
  echo "Timeout($_my_timeout) ran: $*"
  return ${_return:-0}
}

my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true  && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd

mit den Ausgängen:

Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated

Natürlich nehme ich an, dass es ein Verzeichnis namens gab scripts


0
#! /bin/bash
timeout=10
interval=1
delay=3
(
    ((t = timeout)) || :

    while ((t > 0)); do
        echo "$t"
        sleep $interval
        # Check if the process still exists.
        kill -0 $$ 2> /dev/null || exit 0
        ((t -= interval)) || :
    done

    # Be nice, post SIGTERM first.
    { echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &

exec "$@"

@Tino Entschuldigung, ich habe vergessen, warum ich die Prozessbeendigungszeile geändert habe und warum ich dachte, dass dies wichtig ist, um sie zu teilen. Schade, dass ich das nicht aufgeschrieben habe. Vielleicht stellte ich fest, dass ich eine Pause einlegen musste, bevor ich nach einem Erfolg von kill -s TERM suchte. Das Skript für das Jahr 2008 aus dem Kochbuch scheint unmittelbar nach dem Senden von SIGTERM nach dem Status des Prozesses zu suchen, was möglicherweise zu einem Fehler beim Versuch führt, SIGKILL an einen Prozess zu senden, der gestorben ist.
Aal Gheez

0

Mein Problem war vielleicht etwas anders: Ich starte einen Befehl über ssh auf einem Remotecomputer und möchte die Shell und die Kinder töten, wenn der Befehl hängt.

Ich benutze jetzt folgendes:

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

Auf diese Weise gibt der Befehl 255 zurück, wenn eine Zeitüberschreitung aufgetreten ist, oder den Rückkehrcode des Befehls bei Erfolg

Bitte beachten Sie, dass das Beenden von Prozessen aus einer SSH-Sitzung anders gehandhabt wird als eine interaktive Shell. Sie können aber auch die Option -t für ssh verwenden, um ein Pseudo-Terminal zuzuweisen, sodass es sich wie eine interaktive Shell verhält


0

Hier ist eine Version, die nicht auf dem Laichen eines untergeordneten Prozesses beruht. Ich benötigte ein eigenständiges Skript, in das diese Funktionalität eingebettet war. Es wird auch ein gebrochenes Abfrageintervall durchgeführt, sodass Sie schneller abrufen können. Timeout wäre bevorzugt gewesen - aber ich stecke auf einem alten Server fest

# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
    local timeout=$1; shift
    local interval=$1; shift
    $* &
    local child=$!

    loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
    ((t = loops))
    while ((t > 0)); do
        sleep $interval
        kill -0 $child &>/dev/null || return
        ((t -= 1))
    done

    kill $child &>/dev/null || kill -0 $child &>/dev/null || return
    sleep $interval
    kill -9 $child &>/dev/null
    echo Timed out
}

slow_command()
{
    sleep 2
    echo Completed normally
}

# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command

# or call an external command
wait_on_command 1 0.1 sleep 10

-1

Ein sehr vereinfachter Weg:

# command & sleep 5; pkill -9 -x -f "command"

Mit pkill (Option -f ) können Sie Ihren spezifischen Befehl mit Argumenten beenden oder -n angeben, um zu vermeiden, dass alte Prozesse beendet werden.


Sie erkennen, dass dies im Wesentlichen das ist, was der OP in seinem Posten hat und was er angibt, dass er nicht will, oder? Weil es immer auf die volle Schlafverzögerung wartet.
Etan Reisner

-1

Aufbauend auf der Antwort von @ loup ...

Wenn Sie ein Zeitlimit für einen Prozess festlegen und die Ausgabe von Kill Job / PID stummschalten möchten, führen Sie Folgendes aus:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

Dadurch wird der Hintergrundprozess in eine Unterschale eingefügt, sodass die Jobausgabe nicht angezeigt wird.


-3

Ich habe einen Cron-Job, der ein PHP-Skript aufruft und manchmal im PHP-Skript hängen bleibt. Diese Lösung war perfekt für mich.

Ich benutze:

scripttimeout -t 60 /script.php

1
Was ist scripttimeout?
Rudolfbyker
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.