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?
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
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 lestimeout
PID:
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
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.
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 ...
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
:-)