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.

21voto

Tino Punkte 8393

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

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Exemple : _timeout 5 longrunning_command args

  • Exemple : { _timeout 5 producer || echo KABOOM $?; } | consumer

  • Exemple : producer | { _timeout 5 consumer1; consumer2; }

  • Exemple : { while date; do sleep .3; done; } | _timeout 5 cat | less

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

  • Liefert 137, wenn der Befehl beendet wurde, ansonsten den Rückgabewert des Befehls.

  • Funktioniert bei Rohren. (Sie müssen hier nicht in den Vordergrund treten!)

  • Funktioniert auch mit internen Shell-Befehlen oder -Funktionen.

  • Läuft in einer Subshell, also kein Variablenexport in die aktuelle Shell, sorry.

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

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

Anmerkungen:

  • Streng genommen brauchen Sie die ; en ; ) aber es macht die Sache konsistenter für die ; } -Gehäuse. Und die set +b kann wahrscheinlich auch weggelassen werden, aber Vorsicht ist besser als Nachsicht.

  • Außer bei --forground (wahrscheinlich) können Sie alle Varianten umsetzen timeout unterstützt. --preserve-status ist allerdings ein bisschen 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 auf diese Weise jedoch keine Umgebungsvariablen in die umgebende Shell zurückexportieren.

Editar:

Beispiel aus der Praxis: Auszeit __git_ps1 für den Fall, dass es zu lange dauert (z.B. bei langsamen SSHFS-Links):

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

Edit2: Fehlerbehebung. Mir ist aufgefallen, dass exit 137 ist nicht erforderlich und macht _timeout gleichzeitig unzuverlässig.

Bearbeiten3: git ist ein eingefleischter Fan und braucht daher einen doppelten Trick, um zufriedenstellend zu funktionieren.

Edit4: Vergessen ein _ im ersten _timeout für das GIT-Beispiel aus der realen Welt.

18voto

maxy Punkte 4248

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

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

Es ist ein bisschen netter als das coreutils "timeout", weil es etwas ausgibt, wenn der Prozess beendet wird, und es sendet auch SIGKILL nach einiger Zeit standardmäßig.

9voto

pixelbeat Punkte 28985

Siehe auch die http://www.pixelbeat.org/scripts/timeout Skript, dessen Funktionalität in neuere coreutils integriert wurde

9voto

user2099484 Punkte 4033

Timeout ist wahrscheinlich der erste Ansatz, den man versuchen sollte. Möglicherweise benötigen Sie eine Benachrichtigung oder einen anderen Befehl, der ausgeführt wird, wenn es zu einem Timeout kommt. Nach einigem Suchen und Experimentieren bin ich auf folgendes gekommen bash Drehbuch:

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

8voto

strager Punkte 86191

Ziemlich hakelig, aber es funktioniert. Funktioniert nicht, wenn Sie andere Prozesse im Vordergrund haben (bitte helfen Sie mir, dies zu beheben!)

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

Ich glaube sogar, dass Sie es umkehren können, wenn Sie Ihre "Bonus"-Kriterien erfüllen:

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

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