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.
8 réponses
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 )
# 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=$?
j'ai aussi eu cette question et j'ai trouvé deux autres choses très utiles:
- Les SECONDES variable en bash.
- 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).)
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?
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
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
.
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.