web-dev-qa-db-fra.com

Bash: attendez avec timeout

Dans un script Bash, j'aimerais faire quelque chose comme:

app1 &
pidApp1=$!
app2 &
pidApp2=$1

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2

À savoir, lancez deux applications en arrière-plan et donnez-leur 60 secondes pour terminer leur travail. Ensuite, s'ils ne finissent pas dans cet intervalle, tuez-les.

Malheureusement, ce qui précède ne fonctionne pas, car timeout est un exécutable, alors que wait est une commande Shell. J'ai essayé de le changer pour:

timeout 60 bash -c wait $pidApp1 $pidApp2

Mais cela ne fonctionne toujours pas, car wait ne peut être appelé que sur un PID lancé dans le même shell.

Des idées?

41
user1202136

Ecrivez les PID dans des fichiers et démarrez les applications de la manière suivante:

pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo $pid > $pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!

wait $pid
kill $killerPid

Cela créerait un autre processus qui dort pendant le délai d'attente et le tue s'il ne s'est pas terminé jusqu'à présent.

Si le processus se termine plus rapidement, le fichier PID est supprimé et le processus destructeur est terminé.

killChildrenOf est un script qui récupère tous les processus et tue tous les enfants d'un certain PID. Consultez les réponses à cette question pour connaître différentes manières de mettre en œuvre cette fonctionnalité: Meilleure façon de tuer tous les processus enfants

Si vous voulez sortir de BASH, vous pouvez écrire des PID et des délais dans un répertoire et regarder ce répertoire. Toutes les minutes ou à peu près, lisez les entrées et vérifiez quels processus existent encore et si leur délai a expiré.

EDIT Si vous voulez savoir si le processus s'est terminé avec succès, vous pouvez utiliser kill -0 $pid

EDIT2 Ou vous pouvez essayer des groupes de processus. kevinarpe a dit: Pour obtenir le PGID d'un PID (146322): 

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'

Dans mon cas: 145974. Ensuite, PGID peut être utilisé avec une option spéciale de kill pour mettre fin à tous les processus d'un groupe: kill -- -145974

21
Aaron Digulla

Votre exemple et la réponse acceptée sont excessivement compliqués. Pourquoi uniquement utilisez-vous timeout puisque c’est exactement son ​​cas d’utilisation? La commande timeout a même une option intégrée (-k) pour envoyer SIGKILL après l'envoi du signal initial pour terminer la commande (SIGTERM par défaut) si la commande est toujours en cours d'exécution après l'envoi du signal initial (voir man timeout).

Si le script ne nécessite pas nécessairement de wait et de reprendre le contrôle après avoir attendu, c'est simplement une question de

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]

Si tel est le cas, c’est tout aussi facile en enregistrant lestimeoutPID:

pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]

Par exemple.

$ cat t.sh
#!/bin/bash

echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"

.

$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script
54
Adrian Frühwirth

Voici une version simplifiée de la réponse d'Aaron Digulla, qui utilise l'astuce kill -0 laissée par Aaron Digulla dans un commentaire:

app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
kill -0 $killerPid && kill $killerPid

Dans mon cas, je voulais être à la fois set -e -x safe et renvoyer le code d'état, alors j'ai utilisé:

set -e -x
app &
pidApp=$!
( sleep 45 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
status=$?
(kill -0 $killerPid && kill $killerPid) || true

exit $status

Un statut de sortie de 143 indique SIGTERM, presque certainement de notre délai d'attente.

3
Bryan Larsen

J'ai écrit une fonction bash qui attend jusqu'à ce que les PID soient terminés ou jusqu'à expiration du délai d'attente, renvoyant une valeur autre que zéro si le délai d'attente est dépassé et affiche tous les PID non terminés.

function wait_timeout {
  local limit=${@:1:1}
  local pids=${@:2}
  local count=0
  while true
  do
    local have_to_wait=false
    for pid in ${pids}; do
      if kill -0 ${pid} &>/dev/null; then
        have_to_wait=true
      else
        pids=`echo ${pids} | sed -e "s/${pid}//g"`
      fi
    done
    if ${have_to_wait} && (( $count < $limit )); then
      count=$(( count + 1 ))
      sleep 1
    else
      echo ${pids}
      return 1
    fi
  done   
  return 0
}

Pour l'utiliser c'est juste wait_timeout $timeout $PID1 $PID2 ...

1
JonatasTeixeira

Pour mettre dans mon 2c, nous pouvons résumer la solution de Teixeira à:

try_wait() {
    # Usage: [PID]...
    for ((i = 0; i < $#; i += 1)); do
        kill -0 $@ && sleep 0.001 || return 0
    done
    return 1 # timeout or no PIDs
} &>/dev/null

La variable sleep de Bash accepte des fractions de seconde, et 0.001s = 1 ms = 1 KHz = beaucoup de temps. Cependant, UNIX n'a ​​aucune faille en matière de fichiers et de processus. try_wait accomplit très peu.

$ cat &
[1] 16574
$ try_wait %1 && echo 'exited' || echo 'timeout'
timeout
$ kill %1
$ try_wait %1 && echo 'exited' || echo 'timeout'
exited

Nous devons répondre à des questions difficiles pour aller plus loin.

Pourquoi wait aucun paramètre de délai d'expiration? Peut-être parce que les commandes timeout, kill -0 , wait et wait -n peuvent indiquer à la machine plus précisément ce que nous voulons.

Pourquoi wait est-il intégré à Bash en premier lieu, de sorte que timeout wait PID ne fonctionne pas? Peut-être qu’à ce moment-là seulement, Bash pourra mettre en œuvre une gestion correcte du signal.

Considérer:

$ timeout 30s cat &
[1] 6680
$ jobs
[1]+    Running   timeout 30s cat &
$ kill -0 %1 && echo 'running'
running
$ # now meditate a bit and then...
$ kill -0 %1 && echo 'running' || echo 'vanished'
bash: kill: (NNN) - No such process
vanished

Que ce soit dans le monde matériel ou dans les machines, étant donné que nous avons besoin d’un certain terrain pour fonctionner, nous avons aussi besoin d’un terrain sur lequel attendre.

  • Quand kill échoue, vous savez à peine pourquoi. Sauf si vous avez écrit Le processus, ou son manuel, nomme les circonstances, il n’ya aucun moyenpour déterminer une valeur de délai raisonnable.

  • Une fois le processus écrit, vous pouvez implémenter un gestionnaire TERM approprié ou même répondre à "Auf Wiedersehen!". envoyez-le par un tuyau nommé. Alors vous avez de la terre même pour un sort comme try_wait :-)

0
Andreas Spindler