Timeout einen Befehl in bash ohne unnötige Verzögerung
-
22-08-2019 - |
Frage
Diese Antwort href="https://stackoverflow.com/questions/601543"> Befehlszeile zum
schlägt ein 1-Zeilen-Verfahren zur Herstellung eines lang laufenden Befehl von der Befehlszeile bash timeout:
( /path/to/slow command with options ) & sleep 5 ; kill $!
Aber es ist möglich, dass ein gegebener „long-running“ Befehl früher beenden kann als das Timeout. (Nennen wir es eine "typisch-long-running-but-mal-schnell" Befehl oder tlrbsf zum Spaß.)
Also dieser nette 1-liner Ansatz hat ein paar Probleme. Erstens ist die sleep
nicht an Bedingungen geknüpft, so dass ein unerwünschten Untersatz gebunden an der Zeit für die Sequenz genommen zu beenden. Betrachten 30s oder 2 m oder sogar 5 m für den Schlaf, wenn die tlrbsf Befehl beendet in 2 Sekunden - höchst unerwünscht. Zweitens ist die kill
ist bedingungslos, so dass diese Sequenz wird versuchen, einen nicht-laufenden Prozess zu töten und jammert über sie.
So ...
Gibt es eine Möglichkeit ein typisch-long-running-aber-mal-schnell ( "tlrbsf" ) Befehl,
Timeout- eine Bash-Implementierung hat (die andere Frage bereits Perl und C Antworten)
- wird in der früheren der beiden beenden: tlrbsf Programmabbruch oder Timeout abgelaufen
- wird nicht töten nicht vorhandene / nicht-laufende Prozesse (oder optional: wird nicht beschweren über ein schlechten kill)
- nicht über ein 1-Liner sein
- kann unter Cygwin oder Linux läuft
... und für Bonuspunkte, läuft die tlrbsf Befehl in dem Vordergrund und jedem 'Schlaf' oder zusätzlichen Prozess im Hintergrund, so dass der stdin / stdout / stderr der tlrbsf Befehl umgeleitet werden kann, so, als ob es direkt ausgeführt worden war?
Wenn ja, bitte teilen Sie Ihren Code. Wenn nicht, bitte erklären, warum.
Ich habe verbringe eine Weile versucht, das oben genannte Beispiel zu hacken, aber ich bin die Grenze meiner bash Fähigkeiten zu treffen.
Lösung
Ich denke, das ist genau das, was Sie fordern:
http://www.bashcookbook.com/bashinfo/ Quelle / 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 "$@"
Andere Tipps
Sie suchen wahrscheinlich für den timeout
Befehl in coreutils. Da es ein Teil von coreutils ist, ist es technisch eine C-Lösung, aber es ist immer noch coreutils. info timeout
für weitere Details.
Hier ein Beispiel:
timeout 5 /path/to/slow/command with options
Diese Lösung funktioniert unabhängig von bash-Monitor-Modus. Sie können das richtige Signal verwenden zu beenden your_command
#!/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 tötet your_command nach bestimmten Timeout; das Skript wartet auf die langsame Aufgabe und beendet die Beobachter. Beachten Sie, dass wait
nicht mit Prozessen funktioniert, die Kinder einer anderen Schale sind.
Beispiele:
- your_command läuft länger als 2 Sekunden und wurde beendet
your_command 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 vor dem Timeout beendet (20 Sekunden)
your_command 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
Dort gehen Sie:
timeout --signal=SIGINT 10 /path/to/slow command with options
Sie können die SIGINT
und 10
ändern, wie Sie wünschen;)
Ich ziehe "Zeitlimit", die ein Paket zumindest in debian hat.
http://devel.ringlet.net/sysutils/timelimit/
Es ist ein bisschen schöner als das coreutils „Timeout“, weil es etwas gedruckt wird, wenn der Prozess zu töten, und es sendet auch SIGKILL nach einiger Zeit in der Standardeinstellung.
Sie können dies tun, ganz mit bash 4.3
und oben:
_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
-
Anforderungen Bash 4.3 für
wait -n
- 137 Gibt wenn der Befehl getötet wurde, sonst den Rückgabewert des Befehls.
- Werke für Rohre. (Sie brauchen nicht Vordergrund, hier zu gehen!)
- Funktioniert mit internen Shell-Befehlen oder Funktionen auch.
- Läuft in einer Subshell, so dass kein variable Export in den aktuell Shell, sorry.
Wenn Sie den Return-Code nicht benötigen, kann dies noch einfacher gemacht werden:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }
Weitere Informationen:
-
Genau genommen Sie die
;
in; )
nicht brauchen, aber es ist Sache konsequenter auf den; }
-Fall macht. Und dieset +b
kann wahrscheinlich weg gelassen werden, auch, aber sicher ist sicher. -
Mit Ausnahme
--forground
(wahrscheinlich) können Sie implementieren alle Variantentimeout
unterstützt.--preserve-status
ist ein bisschen schwierig, aber. Dies wird für den Leser als Übung;)
Dieses Rezept verwendet werden „natürlich“ in der Schale (so natürlich wie für flock fd
):
(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)
jedoch, wie oben erläutert, können Sie nicht Reexport Umgebungsvariablen in den umgebenden natürlich auf diese Weise Shell.
Edit:
Ein echtes Beispiel: Time out __git_ps1
, falls es zu lang (für Dinge wie langsam SSHFS-Verbindungen) führt:
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }
Edit2: Bugfix. Ich bemerkte, dass exit 137
nicht benötigt wird, und macht _timeout
unzuverlässig zugleich.
Edit3: git
ist ein hartnäckiger, so braucht es eine Doppel Trick satisfyingly zu arbeiten
Edit4: Vergessen eine _
im ersten _timeout
für das reale Welt GIT Beispiel
Siehe auch die http://www.pixelbeat.org/scripts/timeout Skript die Funktionalität, die in neueren coreutils integriert wurde,
Kinda hacky, aber es funktioniert. Funktioniert nicht, wenn Sie andere Vordergrundprozesse haben (bitte helfen Sie mir, dieses Problem zu beheben!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Eigentlich, denke ich Sie es rückgängig machen können, welche die 'Bonus' Kriterien:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
timeout ist wahrscheinlich der erste Ansatz zu versuchen. Sie können eine Benachrichtigung oder einen anderen Befehl benötigen, wenn es auszuführen mal aus. Nach einem recht kleinen Suche und Experimentieren, kam ich mit diesem nach oben bash Skript:
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
Einfache Skript mit Code Klarheit. Zu dem /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
Die Zeiten aus einem Befehl, der zu lange läuft:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
kurz sofort für einen Befehl, der ergänzt:
$ run 10 sleep 2
$
die slowcommand
nach 1 Sekunde Zeitüberschreitung:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
Wenn Sie bereits den Namen des Programms kennen (sich program
annimmt) nach dem Timeout zu beenden (als Beispiel 3
Sekunden), habe ich eine einfache und etwas schmutzig alternative Lösung beitragen kann:
(sleep 3 && killall program) & ./program
Das funktioniert perfekt, wenn ich Benchmark-Prozesse mit Systemaufrufen nennen.
Es gibt auch cratimeout
von 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'
OS X nicht bash 4 noch nicht verwendet, noch es hat / usr / bin / timeout, also hier ist eine Funktion, die ohne das Haus zu brauen oder Macports auf OS X arbeitet, die in / usr / bin / timeout ähnlich ist ( auf der Grundlage von Tino Antwort). Parameter Validierung, Hilfe, Nutzung und Unterstützung für andere Signale ist eine Übung für 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
) }
Ich war mit einem Problem stellt die Schale Kontext zu erhalten und erlaube Timeouts, damit das einzige Problem ist es die Skriptausführung auf dem Timeout zu stoppen - aber es ist in Ordnung mit den Bedürfnissen I vorgestellt wurde:
#!/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
ich natürlich annehmen, dass es war ein Verzeichnis namens scripts
#! /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 "$@"
Mein Problem war vielleicht ein bisschen anders: Ich habe einen Befehl via ssh auf einem entfernten Rechner starten und will die Schale und Childs, wenn der Befehl hängt töten
.ich jetzt verwenden wie folgt vor:
ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'
Auf diese Weise der Befehl 255 zurückgibt, wenn ein Timeout oder die Rückkehr des Befehls im Falle des Erfolgs war
Bitte beachten Sie, dass Prozesse aus einer SSH-Sitzung zu töten behandelt wird unterscheidet sich von einer interaktiven Shell. Sie können aber auch die Option -t verwenden, um ssh einen Pseudo-Terminal zu verteilen, so dass es wirkt wie eine interaktive Shell
Dies ist eine Version, die nicht auf angewiesen ist, ein Kind Prozess Laichen - ich brauchte ein eigenständiges Skript, das diese Funktionalität eingebettet. Es spielt auch eine gebrochene Pollintervall, so dass Sie schneller abfragen können. Timeout hätte es vorgezogen worden - aber ich bin auf einem alten Server steckt
# 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
Aufbauend auf @ loup Antwort ...
Wenn Sie einen Prozess Timeout wollen und zum Schweigen bringen die Abtötung Job / pid Ausgang, führen Sie:
( (sleep 1 && killall program 2>/dev/null) &) && program --version
Damit ist der backgrounded Prozess in ein Sub-Shell, so dass Sie den Job Ausgang nicht sehen.
Ich habe einen cron-Job, der ein PHP-Skript und einige Male ruft es auf PHP-Skript stecken. Diese Lösung war perfekt für mich.
ich benutze:
scripttimeout -t 60 /script.php
In 99% der Fälle ist die Antwort nicht jede Timeout Logik zu implementieren. Timeout-Logik ist in nahezu jeder Situation ein rotes Warnsignal, dass etwas sonst ist falsch und sollte festgelegt werden statt .
Ist Ihr Prozess hängen oder Brechen nach n Sekunden manchmal? Dann finden Sie heraus, warum und beheben, statt.
Als Nebenwirkung tun strager Lösung richtig, Sie müssen warten, verwenden „$ SPID“ anstelle von fg 1, da in Skripten Sie keine Job-Kontrolle (und versuchen, es zu aktivieren, ist dumm). Außerdem fg 1 beruht auf der Tatsache, dass Sie keine anderen Jobs vorher im Skript gestartet wurden, die eine schlechte Annahme ist, zu machen.
Eine sehr simple Art und Weise:
# command & sleep 5; pkill -9 -x -f "command"
mit pkill (Option -f ) Sie Ihren spezifischen Befehl mit Argumenten töten oder -n angeben, um alten Prozess zu vermeiden töten.