394 votes

Comment exécuter une commande avec une limite de temps afin qu'elle soit arrêtée si elle dépasse le seuil de temps imparti?

Cette réponse à Commande de ligne de commande pour tuer automatiquement une commande après un certain temps

propose une méthode en une ligne pour définir une durée limite pour une commande longue à partir de la ligne de commande bash :

( /chemin/vers/commande_lente avec options ) & sleep 5 ; kill $!

Mais il est possible qu'une commande "longue" donnée se termine plus tôt que le délai d'expiration.
(Appelons cela une commande "longue-généralement-longue-mais-parfois-rapide", ou tlrbsf pour s'amuser.)

Cette approche astucieuse en une ligne présente quelques problèmes.
Premièrement, le sleep n'est pas conditionnel, ce qui impose une limite inférieure indésirable au temps nécessaire pour que la séquence se termine. Envisagez 30s ou 2m ou même 5m pour le sleep, lorsque la commande tlrbsf se termine en 2 secondes — très indésirable.
Deuxièmement, le kill est inconditionnel, donc cette séquence tentera de tuer un processus non en cours d'exécution et râlera à ce sujet.

Donc...

Y a-t-il un moyen de définir une durée limite pour une commande "longue-généralement-longue-mais-parfois-rapide" ("tlrbsf") qui

  • dispose d'une implémentation bash (l'autre question dispose déjà de réponses en Perl et en C)
  • se terminera au premier des deux cas : la terminaison du programme tlrbsf, ou la fin du délai
  • ne tuera pas les processus inexistants/non en cours d'exécution (ou, en option : ne se plaindra pas d'un kill erroné)
  • n'a pas besoin d'être en une ligne
  • peut s'exécuter sous Cygwin ou Linux

... et, pour des points bonus

  • exécute la commande tlrbsf en premier plan
  • aucun 'sleep' ou processus supplémentaire en arrière-plan

de manière à ce que l'entrée standard/sortie standard/erreur standard de la commande tlrbsf puisse être redirigée, comme si elle avait été exécutée directement ?

Si c'est le cas, veuillez partager votre code. Sinon, veuillez expliquer pourquoi.

J'ai passé un certain temps à essayer de modifier l'exemple mentionné précédemment mais j'atteins la limite de mes compétences en bash.

5 votes

Une autre question similaire : stackoverflow.com/questions/526782/… (mais je pense que la réponse 'timeout3' ici est bien meilleure).

2 votes

Toute raison de ne pas utiliser l'utilitaire timeout de GNU?

2 votes

timeout est génial! vous pouvez même l'utiliser avec plusieurs commandes (script multi-ligne) : stackoverflow.com/a/61888916/658497

698voto

yingted Points 2444

Vous cherchez probablement la commande timeout dans coreutils. Comme c'est une partie de coreutils, c'est techniquement une solution en C, mais c'est toujours coreutils. info timeout pour plus de détails. Voici un exemple :

timeout 5 /chemin/vers/la/commande/lente avec options

29 votes

Dans Mac, vous pouvez installer cela via Macports ou homebrew.

3 votes

Cela, combiné avec run-one, et curl pour le reporting.

28 votes

Lorsqu'installé via homebrew sur OS X, la commande devient gtimeout

170voto

Juliano Points 13802

Je pense que c'est précisément ce que vous demandez :

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# Le script shell Bash exécute une commande avec une durée limite.
# À l'expiration de la durée limite, SIGTERM (15) est envoyé au processus. Si le signal
# est bloqué, alors le signal SIGKILL (9) le termine.
#
# Basé sur l'exemple de documentation Bash.

# Bonjour Chet,
# veuillez trouver ci-joint un exemple de chronométrage "un peu plus facile"  :-)  à comprendre
# Si vous le trouvez adapté, n'hésitez pas à l'inclure
# n'importe où : la même logique que dans les exemples/scripts originaux,
# une implémentation un peu plus transparente à mon goût.
#
# Dmitry V Golovashkin 

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Durée limite.
declare -i timeout=DEFAULT_TIMEOUT
# Intervalle entre les vérifications si le processus est toujours en cours.
declare -i interval=DEFAULT_INTERVAL
# Délai entre l'envoi du signal SIGTERM et la destruction du processus par SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat < 0)); do
        sleep $interval
        kill -0 $$ || exit 0
        ((t -= interval))
    done

    # Soyez gentil, postez d'abord SIGTERM.
    # Le 'exit 0' ci-dessous sera exécuté si une commande précédente échoue.
    kill -s SIGTERM $$ && kill -0 $$ || exit 0
    sleep $delay
    kill -s SIGKILL $$
) 2> /dev/null &

exec "$@"

0 votes

C'est un astuce astucieuse, en utilisant $$ pour la partie de fond. Et c'est bien que ça va tuer immédiatement un tlrbsf suspendu. Mais ugh, vous devez choisir un intervalle de sondage. Et si vous réglez le sondage trop bas, cela va manger le CPU avec un signalement constant, rendant le tlrbsf encore plus long à s'exécuter!

9 votes

Vous n'avez pas besoin de choisir l'intervalle de sondage, il a une valeur par défaut de 1 seconde, ce qui est assez bon. Et la vérification est très peu coûteuse, le surcoût est négligeable. Je doute que cela rendrait tlrbsf sensiblement plus long. J'ai testé avec sleep 30, et obtenu une différence de 0,000ms entre l'utilisation et la non-utilisation.

7 votes

D'accord, je vois ça maintenant. Et cela répond exactement à mes besoins si vous définissez l'intervalle du sondage == délai d'attente. Fonctionne également dans les pipelines, fonctionne avec l'ensemble de la tâche en arrière-plan, fonctionne avec plusieurs instances et autres travaux en cours. Génial, merci!

49voto

Dmitry Points 131

Cette solution fonctionne indépendamment du mode de surveillance de bash. Vous pouvez utiliser le signal approprié pour terminer votre_commande

#!/bin/sh
( votre_commande ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

Le surveillant tue votre_commande après le délai donné; le script attend la tâche lente et termine le surveillant. Notez que wait ne fonctionne pas avec les processus qui sont des enfants d'un shell différent.

Exemples:

  • votre_commande s'exécute pendant plus de 2 secondes et a été interrompue

votre_commande interrompue

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "votre_commande terminée"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "votre_commande interrompue"
fi
  • votre_commande terminée avant le délai (20 secondes)

votre_commande terminée

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "votre_commande terminée"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "votre_commande interrompue"
fi

2 votes

wait renvoie le statut de sortie du processus sur lequel il attend. Donc si votre commande ne sort pas dans le temps imparti mais avec un statut de sortie différent de zéro, alors la logique ici se comportera comme si elle avait expiré, c'est-à-dire qu'elle affichera votre_commande interrompue. Au lieu de cela, vous pourriez exécuter la commande wait sans un if et ensuite vérifier si le pid $watcher existe toujours, si c'est le cas alors vous savez que vous n'avez pas dépassé le temps imparti.

1 votes

Je n'arrive pas à comprendre pourquoi cela utilise kill dans un cas mais pkill dans l'autre. J'ai dû utiliser pkill pour les deux pour que cela fonctionne correctement. Je m'attendrais à ce que si vous enveloppez une commande dans (), alors vous devrez utiliser pkill pour la tuer. Mais peut-être que cela fonctionne différemment s'il n'y a qu'une seule commande à l'intérieur des ().

0 votes

Que Dieu vous bénisse monsieur :)

18voto

maxy Points 1068

Je préfère "timelimit", qui a au moins un paquet dans debian.

http://devel.ringlet.net/sysutils/timelimit/

C'est un peu plus agréable que "timeout" de la coreutils car il affiche quelque chose lorsqu'il tue le processus, et envoie également SIGKILL après un certain temps par défaut.

0 votes

Il semble ne pas fonctionner très bien :/ $ time timelimit -T2 sleep 10 réel 0m10.003s utilisateur 0m0.000s sys 0m0.000s

3 votes

Utilisez -t2 et non -T2. Le grand -T est le temps entre l'envoi de SIGTERM et l'envoi de SIGKILL.

1 votes

Je voudrais ajouter que la limite de temps 1.8 ne semble pas fonctionner correctement avec fork (timelimit -t1 ./a_forking_prog tue seulement l'un des deux processus), mais timeout fonctionne.

9voto

pixelbeat Points 12073

Voir aussi le script http://www.pixelbeat.org/scripts/timeout dont la fonctionnalité a été intégrée dans les nouvelles coreutils

0 votes

Propre, simple, utilise TERM et non KILL. Bien ! J'avais exploré une solution de piège/attente comme celle-ci lorsque j'avais initialement posé la question.

0 votes

Timeout retourne 124 en cas d'un kill.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X