it-swarm.com.de

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

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

  • hat eine bash-Implementierung (die andere Frage enthält bereits Perl- und C-Antworten)
  • wird am vorherigen der beiden beendet: tlrbsf Programmende oder abgelaufenes Timeout
  • tötet nicht vorhandene/nicht laufende Prozesse nicht (oder optional: (sich beschweren über einen fehlerhaften Kill nicht)
  • muss kein 1-Liner sein
  • kann unter Cygwin oder Linux laufen

... 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.

248
system PAUSE

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]"
135
Juliano

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
464
yingted

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.

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 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
33
Dmitry

Da gehst du hin:

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

sie können SIGINT und 10 beliebig ändern;)

18

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.

17
maxy

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; ) }
  • 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, wenn der Befehl getötet wurde, andernfalls der Rückgabewert des Befehls.
  • Funktioniert für Pfeifen. (Sie müssen hier nicht in den Vordergrund gehen!)
  • Funktioniert auch mit internen Shell-Befehlen oder -Funktionen.
  • Läuft in einer Subshell, daher wird keine Variable in die aktuelle Shell exportiert, sorry.

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.

14
Tino

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

9
pixelbeat

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
8
user2099484

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
7
strager

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
$
3
Lycan

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.

3
loup

Zeitlimit für slowcommand nach 1 Sekunde:

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

2
lance.dolan

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'
2
max32

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
) }
1
NerdMachine

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
0
Neil McGill

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
0
Jehad Buria
#! /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]"
0
eel ghEEz

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

0
Franky_GT

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.

0
mpapis

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.

0
Cody A. Ray