Warum sollte das funktionieren?
timeout 10s echo "foo bar" # foo bar
aber das würde nicht
function echoFooBar {
echo "foo bar"
}
echoFooBar # foo bar
timeout 10s echoFooBar # timeout: failed to run command `echoFooBar': No such file or directory
und wie kann ich es zum Laufen bringen?
Antworten:
timeout
ist ein Befehl - wird also in einem Unterprozess Ihrer Bash-Shell ausgeführt. Daher hat es keinen Zugriff auf Ihre Funktionen, die in Ihrer aktuellen Shell definiert sind.Der
timeout
gegebene Befehl wird als Teilprozess des Timeouts ausgeführt - ein Enkelprozess Ihrer Shell.Sie könnten verwirrt sein, da
echo
sowohl eine Shell integriert als auch ein separater Befehl ist.Was Sie tun können, ist, Ihre Funktion in eine eigene Skriptdatei zu stellen, sie so zu ändern, dass sie ausführbar ist, und sie dann mit auszuführen
timeout
.Alternativ können Sie Ihre Funktion in einer Unter-Shell ausführen - und im ursprünglichen Prozess den Fortschritt überwachen und den Unterprozess beenden, wenn er zu lange dauert.
quelle
timeout
Prozesse beendet werden, indem Signale gesendet werden - das können Sie nur für Prozesse tun. Daher muss alles, was Sie mit Timeout ausführen, ein eigener Prozess sein.Wie Douglas Leeder sagte, benötigen Sie einen separaten Prozess, um eine Zeitüberschreitung zu signalisieren. Problemumgehung durch Exportieren der Funktion in Unterschalen und manuelles Ausführen der Unterschale.
export -f echoFooBar timeout 10s bash -c echoFooBar
quelle
Es gibt eine Inline-Alternative, die auch einen Unterprozess der Bash-Shell startet:
timeout 10s bash <<EOT function echoFooBar { echo foo } echoFooBar sleep 20 EOT
quelle
export -f parent_func
(oderset -o allexport
für alle Funktionen im Voraus) der übergeordnete Shell-Prozess aktiviert ist.Sie können eine Funktion erstellen, mit der Sie das gleiche wie Timeout ausführen können, aber auch für andere Funktionen:
function run_cmd { cmd="$1"; timeout="$2"; grep -qP '^\d+$' <<< $timeout || timeout=10 ( eval "$cmd" & child=$! trap -- "" SIGTERM ( sleep $timeout kill $child 2> /dev/null ) & wait $child ) }
Und könnte wie folgt laufen:
run_cmd "echoFooBar" 10
Hinweis: Die Lösung ergab sich aus einer meiner Fragen: Elegante Lösung zur Implementierung eines Timeouts für Bash-Befehle und -Funktionen
quelle
wait $child
? es tut nichts schädliches (außer zu warten), aber es zählt immer noch, selbst wenn das Kind fertig isttimeout_child () { trap -- "" SIGTERM; child=$!; timeout=$1; ( sleep $timeout; kill $child; ) & wait $child; }
Und die Verwendung:( while true; do echo -n .; sleep 0.1; done) & timeout_child 2
Wenn Sie nur Timeout als zusätzliche Option für das gesamte vorhandene Skript hinzufügen möchten, können Sie es auf die Timeout-Option testen lassen und es dann ohne diese Option selbst rekursiv aufrufen.
example.sh:
#!/bin/bash if [ "$1" == "-t" ]; then timeout 1m $0 $2 else #the original script echo $1 sleep 2m echo YAWN... fi
Ausführen dieses Skripts ohne Zeitüberschreitung:
$./example.sh -other_option # -other_option # YAWN...
Ausführen mit einer Zeitüberschreitung von einer Minute:
$./example.sh -t -other_option # -other_option
quelle
function foo(){ for i in {1..100}; do echo $i; sleep 1; done; } cat <( foo ) # Will work timeout 3 cat <( foo ) # Will Work timeout 3 cat <( foo ) | sort # Wont work, As sort will fail cat <( timeout 3 cat <( foo ) ) | sort -r # Will Work
quelle
Diese Funktion verwendet nur integrierte Funktionen
Vielleicht sollten Sie "$ *" auswerten, anstatt $ @ direkt auszuführen, je nach Ihren Anforderungen
Es startet einen Job mit der Befehlszeichenfolge, die nach dem ersten Argument angegeben wird, das der Timeout-Wert ist, und überwacht die Job-PID
Es überprüft alle 1 Sekunden, Bash unterstützt Timeouts bis zu 0,01, so dass optimiert werden kann
Auch wenn Ihr Skript stdin benötigt,
read
sollte es sich auf ein dediziertes fd (exec {tofd}<> <(:)
) verlassenAuch sollten Sie das Kill - Signal (die innerhalb der Schleife) zu optimieren , die standardmäßig auf
-15
, möchten Sie vielleicht-9
## forking is evil timeout() { to=$1; shift $@ & local wp=$! start=0 while kill -0 $wp; do read -t 1 start=$((start+1)) if [ $start -ge $to ]; then kill $wp && break fi done }
quelle
Setzen Sie meinen Kommentar zu Tiago Lopos Antwort in eine besser lesbare Form:
Ich denke, es ist besser lesbar, der neuesten Subshell eine Zeitüberschreitung aufzuerlegen. Auf diese Weise müssen wir keine Zeichenfolge auswerten, und das gesamte Skript kann von Ihrem bevorzugten Editor als Shell hervorgehoben werden. Ich setze einfach die Befehle, nachdem die Subshell mit
eval
in eine Shell-Funktion übergegangen ist (getestet mit zsh, sollte aber mit bash funktionieren):timeout_child () { trap -- "" SIGTERM child=$! timeout=$1 ( sleep $timeout kill $child ) & wait $child }
Anwendungsbeispiel:
( while true; do echo -n .; sleep 0.1; done) & timeout_child 2
Und auf diese Weise funktioniert es auch mit einer Shell-Funktion (wenn sie im Hintergrund ausgeführt wird):
print_dots () { while true do sleep 0.1 echo -n . done } > print_dots & timeout_child 2 [1] 21725 [3] 21727 ...................[1] 21725 terminated print_dots [3] + 21727 done ( sleep $timeout; kill $child; )
quelle
Ich habe eine geringfügige Änderung der Antwort von @Tiago Lopo, die Befehle mit mehreren Argumenten verarbeiten kann. Ich habe auch die Lösung von TauPan getestet, aber sie funktioniert nicht, wenn Sie sie mehrmals in einem Skript verwenden, während dies bei Tiago der Fall ist.
function timeout_cmd { local arr local cmd local timeout arr=( "$@" ) # timeout: first arg # cmd: the other args timeout="${arr[0]}" cmd=( "${arr[@]:1}" ) ( eval "${cmd[@]}" & child=$! echo "child: $child" trap -- "" SIGTERM ( sleep "$timeout" kill "$child" 2> /dev/null ) & wait "$child" ) }
Hier ist ein voll funktionsfähiges Skript, mit dem Sie die obige Funktion testen können:
$ ./test_timeout.sh -h Usage: test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT] test_timeout.sh -h Test timeout_cmd function. Options: -n Dry run, do not actually sleep. -r REPEAT Reapeat everything multiple times [default: 1]. -s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5]. -t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout].
Zum Beispiel können Sie wie folgt starten:
$ ./test_timeout.sh -r 2 -s 5 -t 3 Try no: 1 - Set timeout to: 3 child: 2540 -> retval: 143 -> The command timed out Try no: 2 - Set timeout to: 3 child: 2593 -> retval: 143 -> The command timed out Done!
#!/usr/bin/env bash #shellcheck disable=SC2128 SOURCED=false && [ "$0" = "$BASH_SOURCE" ] || SOURCED=true if ! $SOURCED; then set -euo pipefail IFS=$'\n\t' fi #################### helpers function check_posint() { local re='^[0-9]+$' local mynum="$1" local option="$2" if ! [[ "$mynum" =~ $re ]] ; then (echo -n "Error in option '$option': " >&2) (echo "must be a positive integer, got $mynum." >&2) exit 1 fi if ! [ "$mynum" -gt 0 ] ; then (echo "Error in option '$option': must be positive, got $mynum." >&2) exit 1 fi } #################### end: helpers #################### usage function short_usage() { (>&2 echo \ "Usage: test_timeout.sh [-n] [-r REPEAT] [-s SLEEP_TIME] [-t TIMEOUT] test_timeout.sh -h" ) } function usage() { (>&2 short_usage ) (>&2 echo \ " Test timeout_cmd function. Options: -n Dry run, do not actually sleep. -r REPEAT Reapeat everything multiple times [default: 1]. -s SLEEP_TIME Sleep for SLEEP_TIME seconds [default: 5]. -t TIMEOUT Timeout after TIMEOUT seconds [default: no timeout]. ") } #################### end: usage help_flag=false dryrun_flag=false SLEEP_TIME=5 TIMEOUT=-1 REPEAT=1 while getopts ":hnr:s:t:" opt; do case $opt in h) help_flag=true ;; n) dryrun_flag=true ;; r) check_posint "$OPTARG" '-r' REPEAT="$OPTARG" ;; s) check_posint "$OPTARG" '-s' SLEEP_TIME="$OPTARG" ;; t) check_posint "$OPTARG" '-t' TIMEOUT="$OPTARG" ;; \?) (>&2 echo "Error. Invalid option: -$OPTARG.") (>&2 echo "Try -h to get help") short_usage exit 1 ;; :) (>&2 echo "Error.Option -$OPTARG requires an argument.") (>&2 echo "Try -h to get help") short_usage exit 1 ;; esac done if $help_flag; then usage exit 0 fi #################### utils if $dryrun_flag; then function wrap_run() { ( echo -en "[dry run]\\t" ) ( echo "$@" ) } else function wrap_run() { "$@"; } fi # Execute a shell function with timeout # https://stackoverflow.com/a/24416732/2377454 function timeout_cmd { local arr local cmd local timeout arr=( "$@" ) # timeout: first arg # cmd: the other args timeout="${arr[0]}" cmd=( "${arr[@]:1}" ) ( eval "${cmd[@]}" & child=$! echo "child: $child" trap -- "" SIGTERM ( sleep "$timeout" kill "$child" 2> /dev/null ) & wait "$child" ) } #################### function sleep_func() { local secs local waitsec waitsec=1 secs=$(($1)) while [ "$secs" -gt 0 ]; do echo -ne "$secs\033[0K\r" sleep "$waitsec" secs=$((secs-waitsec)) done } command=("wrap_run" \ "sleep_func" "${SLEEP_TIME}" ) for i in $(seq 1 "$REPEAT"); do echo "Try no: $i" if [ "$TIMEOUT" -gt 0 ]; then echo " - Set timeout to: $TIMEOUT" set +e timeout_cmd "$TIMEOUT" "${command[@]}" retval="$?" set -e echo " -> retval: $retval" # check if (retval % 128) == SIGTERM (== 15) if [[ "$((retval % 128))" -eq 15 ]]; then echo " -> The command timed out" fi else echo " - No timeout" "${command[@]}" retval="$?" fi done echo "Done!" exit 0
quelle
Dieser eine Liner beendet Ihre Bash-Sitzung nach 10 Sekunden
$ TMOUT=10 && echo "foo bar"
quelle