Diese Antwort an Befehlszeilenbefehl, um einen Befehl nach einer bestimmten Zeit automatisch zu beenden
schlägt eine einzeilige Methode vor, um einen lang andauernden Befehl von der Bash-Befehlszeile aus zu zeitigen:
( /path/to/slow command with options ) & sleep 5 ; kill $!
Es ist jedoch möglich, dass ein gegebener Befehl mit langer Laufzeit vor dem Timeout beendet wird. (Nennen wir es einen "normalerweise-langen-aber-manchmal-schnellen" Befehl oder tlrbsf, um Spaß zu haben.)
Dieser clevere 1-Liner-Ansatz hat also einige Probleme. Erstens ist sleep
nicht bedingt, so dass eine unerwünschte Untergrenze für die Zeit festgelegt wird, die die Sequenz zum Abschluss benötigt. Erwägen Sie 30 Sekunden oder 2 Meter oder sogar 5 Meter für den Schlaf, wenn der Befehl tlrbsf in 2 Sekunden beendet ist - höchst unerwünscht. Zweitens ist kill
unbedingt erforderlich, sodass diese Sequenz versucht, einen nicht laufenden Prozess abzubrechen und darüber zu jammern.
So...
Gibt es eine Möglichkeit, einen normalerweise lang laufenden, aber manchmal schnellen Befehl ("tlrbsf") das Zeitlimit festzulegen
... und für Bonuspunkte wird der Befehl tlrbsf im Vordergrund und ein beliebiger "Schlaf" - oder zusätzlicher Prozess im Hintergrund ausgeführt, so dass der Befehl stdin/stdout/stderr des tlrbsf Befehl kann umgeleitet werden, als ob er direkt ausgeführt wurde?
Wenn ja, bitte teilen Sie Ihren Code. Wenn nicht, erklären Sie bitte warum.
Ich habe eine Weile versucht, das oben erwähnte Beispiel zu hacken, aber ich stelle die Grenze meiner bash-Fähigkeiten ein.
Ich denke, das ist genau das, wonach Sie fragen:
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 <[email protected]>
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 "[email protected]"
Sie suchen wahrscheinlich den Befehl timeout
in coreutils. Da es ein Teil von coreutils ist, handelt es sich technisch gesehen um 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 vom Basismonitormodus. 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 tötet den Befehl nach einem bestimmten Timeout. Das Skript wartet auf die langsame Task und beendet den Beobachter. Beachten Sie, dass wait
nicht mit Prozessen arbeitet, die einer anderen Shell untergeordnet sind.
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 ist fertig
( 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
Da gehst du hin:
timeout --signal=SIGINT 10 /path/to/slow command with options
sie können SIGINT
und 10
beliebig ändern;)
Ich bevorzuge "timelimit", das zumindest in debian ein Paket hat.
http://devel.ringlet.net/sysutils/timelimit/
Es ist ein bisschen schöner als das "timeout" von coreutils, weil es etwas druckt, wenn der Prozess abgebrochen wird, und SIGKILL standardmäßig nach einiger Zeit gesendet wird.
Sie können dies vollständig mit bash 4.3
und höher tun:
_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
_timeout 5 longrunning_command args
{ _timeout 5 producer || echo KABOOM $?; } | consumer
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
Wenn Sie den Rückkehrcode 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 den ;
nicht in ; )
, jedoch wird die Sache mit dem ; }
- Fall konsistent. Und der set +b
kann wahrscheinlich auch weggelassen werden, aber sicherer als leid.
Mit Ausnahme von --forground
(wahrscheinlich) können Sie alle Varianten, die timeout
unterstützt, implementieren. --preserve-status
ist allerdings etwas schwierig. Dies ist eine Übung für den Leser;)
Dieses Rezept kann "natürlich" in der Shell 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 umgebende Shell exportieren.
Bearbeiten:
Praxisbeispiel: Timeout __git_ps1
falls es zu lange dauert (für langsame SSHFS-Links):
eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "[email protected]"; }; _timeout 0.3 __orig__git_ps1 "[email protected]"; ) }
Edit2: Bugfix. Ich habe festgestellt, dass exit 137
nicht benötigt wird und _timeout
gleichzeitig unzuverlässig macht.
Edit3: git
ist ein hartes Stück, also braucht es einen doppelten Trick, um zufriedenstellend zu arbeiten.
Edit4: Vergiss einen _
im ersten _timeout
für das reale GIT-Beispiel.
Siehe auch das Skript http://www.pixelbeat.org/scripts/timeout , dessen Funktionalität in neuere coreutils integriert wurde
timeout ist wahrscheinlich der erste Ansatz, der versucht wird. Es kann eine Benachrichtigung oder ein anderer Befehl zur Ausführung erforderlich sein, wenn das Zeitlimit überschritten wird. Nach einigem Suchen und Experimentieren fand ich dieses bash script:
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
Irgendwie hackig, aber es funktioniert. Funktioniert nicht, wenn Sie andere Vordergrundprozesse haben (bitte helfen Sie mir, das Problem zu beheben!)
sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Ich glaube, Sie können es rückgängig machen und Ihre Bonuskriterien erfüllen:
(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
Einfaches Skript mit Code-Klarheit. In /usr/local/bin/run
speichern:
#!/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
"[email protected]"
# Stop timeout
kill $timeout_pid &>/dev/null
Gibt einen Befehl aus, der zu lang läuft:
$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Wird sofort beendet, wenn ein Befehl ausgeführt wird:
$ run 10 sleep 2
$
Wenn Sie den Namen des Programms bereits kennen (nehmen wir an, program
), dass es nach dem Timeout beendet wird (als Beispiel 3
Sekunden), kann ich eine einfache und etwas schmutzige alternative Lösung einbringen:
(sleep 3 && killall program) & ./program
Dies funktioniert perfekt, wenn ich mit Systemaufrufen Benchmark-Prozesse abrufe.
Zeitlimit für slowcommand
nach 1 Sekunde:
timeout 1 slowcommand || echo "I failed, perhaps due to time out"
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 verwendet noch nicht bash 4, noch hat/usr/bin/timeout eine Funktion, die unter OS X ohne Home-Brew oder Macports funktioniert und ähnlich zu/usr/bin/timeout ist (basierend auf Tino's Antworten). Parameterüberprüfung, Hilfe, Verwendung und Unterstützung für andere Signale sind 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
) }
Hier ist eine Version, die nicht darauf angewiesen ist, einen untergeordneten Prozess zu starten - ich brauchte ein eigenständiges Skript, in das diese Funktionalität eingebettet ist. Es führt auch ein fraktionales Abfrageintervall aus, damit Sie schneller abfragen können. Timeout wäre vorgezogen worden - 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
Ich habe einen Cron-Job, der ein PHP-Skript aufruft, und manchmal bleibt es bei PHP-Skript hängen. Diese Lösung war perfekt für mich.
Ich benutze:
scripttimeout -t 60 /script.php
#! /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 "[email protected]"
Mein Problem war vielleicht etwas anders: Ich starte einen Befehl über ssh auf einem Remote-Rechner und möchte die Shell und die Childs abtöten, wenn der Befehl hängen bleibt.
Ich verwende 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 ein Timeout oder der Rückkehrcode des Befehls aufgetreten ist, falls dies erfolgreich war
Bitte beachten Sie, dass das Abschließen von Prozessen aus einer SSH-Sitzung anders als bei einer interaktiven Shell erfolgt. Sie können jedoch auch die Option -t für ssh verwenden, um ein Pseudo-Terminal zuzuweisen, sodass es sich wie eine interaktive Shell verhält
Ich hatte ein Problem, um den Shell-Kontext zu bewahren und Timeouts zuzulassen. Das einzige Problem besteht darin, dass die Skriptausführung nach Ablauf des Timeouts angehalten wird. Dies ist jedoch gut für die Anforderungen, die mir präsentiert 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=$!
"[email protected]" || _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 gehe ich davon aus, dass es ein Verzeichnis namens scripts
gab.
Aufbauend auf @ loups Antwort ...
Wenn Sie einen Prozess zeitlich abbrechen und die Ausgabe des Kill-Jobs/PID stummschalten möchten, führen Sie Folgendes aus
( (sleep 1 && killall program 2>/dev/null) &) && program --version
Dadurch wird der Hintergrundprozess in eine Subshell verschoben, sodass die Jobausgabe nicht angezeigt wird.