Comment tuer un enfant processus après un délai donné à Bash?

j'ai un script bash qui lance un processus enfant qui plante (en fait, pend) de temps en temps et sans raison apparente (source fermée, donc il n'y a pas grand chose que je puisse faire). En conséquence, j'aimerais pouvoir lancer ce processus pour une période de temps donnée, et le tuer s'il n'est pas revenu avec succès après une période de temps donnée.

y a-t-il un simple et robuste moyen d'atteindre que l'utilisation bash?

P.S.: dites-moi si cette question convient mieux à serverfault ou super-utilisateur.

133
demandé sur codeforester 2011-03-02 01:27:37

8 réponses

: FAQ de BASH entrée # 68: "Comment puis-je exécuter une commande, et l'avoir avorter (timeout) après N secondes?" )

si cela ne vous dérange pas de télécharger quelque chose, utilisez timeout ( sudo apt-get install timeout ) et utilisez-le comme: (la plupart des systèmes l'ont déjà installé sinon utilisez sudo apt-get install coreutils )

timeout 10 ping www.goooooogle.com

si vous ne voulez pas télécharger quelque chose, faites ce que timeout fait en interne:

( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )

dans le cas où vous voulez faire un timeout pour du code bash plus long, utilisez la deuxième option comme telle:

( cmdpid=$BASHPID; 
    (sleep 10; kill $cmdpid) \
   & while ! ping -w 1 www.goooooogle.com 
     do 
         echo crap; 
     done )
209
répondu Ignacio Vazquez-Abrams 2018-04-30 12:09:26
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &

ou pour obtenir les codes de sortie aussi bien:

# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
26
répondu Dan 2011-03-01 22:46:20
sleep 999&
t=$!
sleep 10
kill $t
10
répondu DigitalRoss 2011-03-01 22:35:53

j'ai aussi eu cette question et j'ai trouvé deux autres choses très utiles:

  1. Les SECONDES variable en bash.
  2. la commande "pgrep".

donc j'utilise quelque chose comme ça sur la ligne de commande (OSX 10.9):

ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done

comme il s'agit d'une boucle, j'ai inclus un" sleep 0.2 " pour garder le CPU au frais. ;- )

(BTW: ping est un mauvais exemple, de toute façon, vous avez juste serait d'utiliser la option intégrée "-t " (timeout).)

3
répondu Ulrich 2014-11-08 00:37:17

en supposant que vous avez (ou que vous pouvez facilement créer) un fichier pid pour suivre le pid de l'enfant, vous pouvez alors créer un script qui vérifie le modtime du fichier pid et tue/respawns le processus si nécessaire. Ensuite, il suffit de mettre le script dans crontab pour exécuter approximativement la période dont vous avez besoin.

faites-moi savoir si vous avez besoin de plus de détails. Si ça ne semble pas adapté à vos besoins, qu'en est-il de upstart?

1
répondu kojiro 2011-03-01 22:33:27

une façon est d'exécuter le programme dans un sous-puits, et de communiquer avec le sous-puits à travers un tuyau nommé avec la commande read . De cette façon, vous pouvez vérifier l'état de sortie du processus en cours d'exécution et le communiquer à travers le tuyau.

voici un exemple de chronométrage de la commande yes après 3 secondes. Il obtient le PID du processus en utilisant pgrep (ne fonctionne peut-être que sur Linux). Il ya aussi un certain problème avec l'utilisation d'un tuyau dans ce processus l'ouverture d'un tuyau pour lire sera suspendue jusqu'à ce qu'elle soit aussi ouverte pour écrire, et vice versa. Donc, pour empêcher la suspension de la commande read , j'ai" coincé " ouvrir le tuyau pour lire avec un fond subshell. (Une autre façon d'empêcher un gel pour ouvrir la lecture-écriture de la pipe, i.e. read -t 5 <>finished.pipe - cependant, cela peut aussi ne pas fonctionner sauf avec Linux.)

rm -f finished.pipe
mkfifo finished.pipe

{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!

# Get command PID
while : ; do
    PID=$( pgrep -P $SUBSHELL yes )
    test "$PID" = "" || break
    sleep 1
done

# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &  

read -t 3 FINISHED <finished.pipe

if [ "$FINISHED" = finished ] ; then
  echo 'Subprocess finished'
else
  echo 'Subprocess timed out'
  kill $PID
fi

rm finished.pipe
1
répondu Gavin Smith 2014-08-10 14:45:52

voici une tentative qui tente d'éviter de tuer un processus après qu'il a déjà quitté, ce qui réduit la chance de tuer un autre processus avec le même ID de processus (bien qu'il soit probablement impossible d'éviter ce genre d'erreur complètement).

run_with_timeout ()
{
  t=
  shift

  echo "running \"$*\" with timeout $t"

  (
  # first, run process in background
  (exec sh -c "$*") &
  pid=$!
  echo $pid

  # the timeout shell
  (sleep $t ; echo timeout) &
  waiter=$!
  echo $waiter

  # finally, allow process to end naturally
  wait $pid
  echo $?
  ) \
  | (read pid
     read waiter

     if test $waiter != timeout ; then
       read status
     else
       status=timeout
     fi

     # if we timed out, kill the process
     if test $status = timeout ; then
       kill $pid
       exit 99
     else
       # if the program exited normally, kill the waiting shell
       kill $waiter
       exit $status
     fi
  )
}

utiliser comme run_with_timeout 3 sleep 10000 , qui court sleep 10000 mais se termine après 3 secondes.

c'est comme les autres réponses qui utilisent un processus de timeout de fond pour tuer le processus enfant après retard. Je pense que C'est presque la même chose que la réponse étendue de Dan ( https://stackoverflow.com/a/5161274/1351983 ), sauf que le shell de temporisation ne sera pas tué s'il est déjà terminé.

après la fin de ce programme, il y aura encore quelques processus de "sommeil" en cours, mais ils devraient être inoffensifs.

c'est peut-être une meilleure solution que mon autre réponse parce qu'elle n'utilise pas la fonctionnalité Non portable shell read -t et n'utilise pas pgrep .

0
répondu Gavin Smith 2017-05-29 19:08:53

voici ma troisième réponse. Celui-ci gère les interruptions de signal et nettoie les processus d'arrière-plan lorsque SIGINT est reçu. Il utilise le $BASHPID et exec astuce utilisée dans le réponse sommet pour obtenir le PID d'un processus (dans ce cas $$ dans un sh l'invocation). Il utilise un FIFO pour communiquer avec un sous-puits qui est responsable de tuer et de nettoyer. (C'est comme la pipe dans mon deuxième réponse , mais avoir un tuyau nommé signifie que le gestionnaire de signal peut y écrire aussi.)

run_with_timeout ()
{
  t= ; shift

  trap cleanup 2

  F=$$.fifo ; rm -f $F ; mkfifo $F

  # first, run main process in background
  "$@" & pid=$!

  # sleeper process to time out
  ( sh -c "echo $$ >$F ; exec sleep $t" ; echo timeout >$F ) &
  read sleeper <$F

  # control shell. read from fifo.
  # final input is "finished".  after that
  # we clean up.  we can get a timeout or a
  # signal first.
  ( exec 0<$F
    while : ; do
      read input
      case $input in
        finished)
          test $sleeper != 0 && kill $sleeper
          rm -f $F
          exit 0
          ;;
        timeout)
          test $pid != 0 && kill $pid
          sleeper=0
          ;;
        signal)
          test $pid != 0 && kill $pid
          ;;
      esac
    done
  ) &

  # wait for process to end
  wait $pid
  status=$?
  echo finished >$F
  return $status
}

cleanup ()
{
  echo signal >$$.fifo
}

j'ai essayé d'éviter les conditions de course autant que possible. Cependant, une source d'erreur que je n'ai pas pu supprimer est lorsque le processus se termine presque au même moment que le délai. Par exemple, run_with_timeout 2 sleep 2 ou run_with_timeout 0 sleep 0 . Pour moi, ce dernier donne une erreur:

timeout.sh: line 250: kill: (23248) - No such process

comme il essaie de tuer un processus qui a déjà quitté par lui-même.

0
répondu Gavin Smith 2017-06-04 12:07:38