Comment introduire le délai d'attente pour les scripts shell?

35

Je veux exécuter un script shell qui a une boucle et qui peut aller pour toujours, ce que je ne veux pas. J'ai donc besoin d'introduire un délai d'attente pour l'ensemble du script.

Comment puis-je introduire un délai d'attente pour l'ensemble du script shell sous SuSE?

Radek
la source
4
C'est une question très vague. Pouvez-vous préciser quel type de délai d'attente? Voulez-vous un délai d'expiration pour l'ensemble du script ou un délai d'expiration pour une seule fonction ou commande? Qu'essayez-vous de faire?
Tim Kennedy le
@ TimKennedy: espérons que c'est mieux maintenant.
Radek
1
Voir aussi Expiration du délai dans un script shell (je ne considère pas cela comme un doublon en raison de mon exigence de portabilité, qui exclut des outils tels que timeoutou expect)
Gilles 'SO, arrête d'être méchant'

Réponses:

30

Si GNU timeoutn’est pas disponible, vous pouvez utiliser expect(Mac OS X, BSD, ... n’ont généralement pas d’outils et d’utilitaires GNU par défaut).

################################################################################
# Executes command with a timeout
# Params:
#   $1 timeout in seconds
#   $2 command
# Returns 1 if timed out 0 otherwise
timeout() {

    time=$1

    # start the command in a subshell to avoid problem with pipes
    # (spawn accepts one command)
    command="/bin/sh -c \"$2\""

    expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 1 } eof { exit 0 }"    

    if [ $? = 1 ] ; then
        echo "Timeout after ${time} seconds"
    fi

}

Exemple d' édition :

timeout 10 "ls ${HOME}"
Matteo
la source
Cela semble bon. Comment puis-je voir la sortie de la commande exécutée à l'écran?
Radek
Bonjour, le résultat doit être visible car stdout n’est redirigé nulle part. J'ai ajouté un exemple pour l'utilisation. Dans mon cas, le contenu de mon répertoire personnel est imprimé comme prévu. Quelle commande n'imprime aucune sortie?
Matteo
Je ne l'ai pas appelé correctement. Cela fonctionne vraiment bien! Merci. Seulement, il ne produit pas le nombre de secondes après expiration du délai.
Radek
@Radek Désolé, corrigé
Matteo
1
J'aimerais souligner que si vous utilisez le délai d'expiration GNU, son statut de retour timeoutest 124 dans le cas d'un délai d'attente, sinon il s'agit du statut de retour de la commande (cela est mentionné dans la réponse de Tim Kennedy).
QuasarDonkey
15

Merci pour la clarification.

Le moyen le plus simple d'accomplir ce que vous recherchez est d'exécuter votre script avec la boucle dans un wrapper similaire à la timeoutcommande du package GNU Coreutils.

root@coraid-sp:~# timeout --help            
Usage: timeout [OPTION] DURATION COMMAND [ARG]...
   or: timeout [OPTION]
Start COMMAND, and kill it if still running after DURATION.

Mandatory arguments to long options are mandatory for short options too.
  -k, --kill-after=DURATION
                   also send a KILL signal if COMMAND is still running
                   this long after the initial signal was sent.
  -s, --signal=SIGNAL
                   specify the signal to be sent on timeout.
                   SIGNAL may be a name like 'HUP' or a number.
                   See `kill -l` for a list of signals
      --help     display this help and exit
      --version  output version information and exit

DURATION is an integer with an optional suffix:
`s' for seconds(the default), `m' for minutes, `h' for hours or `d' for days.

If the command times out, then exit with status 124.  Otherwise, exit
with the status of COMMAND.  If no signal is specified, send the TERM
signal upon timeout.  The TERM signal kills any process that does not
block or catch that signal.  For other processes, it may be necessary to
use the KILL (9) signal, since this signal cannot be caught.

Report timeout bugs to [email protected]
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'timeout invocation'

En fin de compte, ce sera beaucoup plus facile que d’écrire votre propre fonction de délai d’attente, que les shells n’ont généralement pas intégrée.

Tim Kennedy
la source
Je ne savais pas tout de suite que je l'avais installée, car elle s'appelle gtimeoutsur ma configuration (OSX).
Joshua Goldberg
7

Lancez un processus de surveillance à partir de votre script pour tuer son parent s'il est exécuté trop longtemps. Exemple:

# watchdog process
mainpid=$$
(sleep 5; kill $mainpid) &
watchdogpid=$!

# rest of script
while :
do
   ...stuff...
done
kill $watchdogpid

Ce script sera terminé par le chien de garde au bout de cinq secondes.

Kyle Jones
la source
6
Mais vous devrez attendre le délai d'attente à chaque fois. Dans votre exemple, votre script sera toujours exécuté 5 secondes, même s'il est beaucoup plus court. Vous devez également stocker le PID du chien de garde et le tuer à la fin.
Matteo
@ Matteo assez juste. Ajoutée.
Kyle Jones
1
Notez que cela peut être plus compliqué que cela, en fonction de ce que le ... matériel ... fait. Voir Timing out dans un script shell
Gilles 'SO- arrête d'être méchant'
lire aussi: combien de temps les PID peuvent-ils être considérés comme uniques stackoverflow.com/questions/11323410/linux-pid-recycling
Florian Castellane
3

Il y a aussi cratimeoutde Martin Cracauer.

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
Jackz
la source
2
#!/bin/sh

# Execute a command with a timeout

if [ "$#" -lt "2" ]; then
echo "Usage:   `basename $0` timeout_in_seconds command" >&2
echo "Example: `basename $0` 2 sleep 3 || echo timeout" >&2
exit 1
fi

cleanup()
{
trap - ALRM               #reset handler to default
kill -ALRM $a 2>/dev/null #stop timer subshell if running
kill $! 2>/dev/null &&    #kill last job
  exit 124                #exit with 124 if it was running
}

watchit()
{
trap "cleanup" ALRM
sleep $1& wait
kill -ALRM $$
}

watchit $1& a=$!         #start the timeout
shift                    #first param was timeout for sleep
trap "cleanup" ALRM INT  #cleanup after timeout
"$@"& wait $!; RET=$?    #start the job wait for it and save its return value
kill -ALRM $a            #send ALRM signal to watchit
wait $a                  #wait for watchit to finish cleanup
exit $RET                #return the value
Vivek Ragupathy
la source