Le mieux serait d'utiliser la timeout
commande si vous l'avez, ce qui est fait pour ça:
timeout 86400 cmd
L’implémentation GNU actuelle (8.23) fonctionne au moins en utilisant alarm()
ou l’équivalent en attendant le processus enfant. Il ne semble pas se prémunir contre la SIGALRM
livraison entre le waitpid()
retour et la timeout
sortie (ce qui annule effectivement cette alarme ). Pendant cette petite fenêtre, il timeout
est même possible d’écrire des messages sur stderr (par exemple si l’enfant dumpait un noyau), ce qui élargirait encore cette fenêtre de course (indéfiniment si stderr est un tube plein, par exemple).
Personnellement, je peux vivre avec cette limitation (qui sera probablement corrigée dans une future version). timeout
fera également très attention de signaler le bon état de sortie, de gérer les autres cas (comme SIGALRM bloqué / ignoré au démarrage, de traiter les autres signaux ...) mieux que vous ne le feriez probablement à la main.
À titre approximatif, vous pouvez l'écrire perl
comme suit:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
wait;
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
} else {exec @ARGV}' cmd
Il y a une timelimit
commande sur http://devel.ringlet.net/sysutils/timelimit/ (devance GNU timeout
de quelques mois).
timelimit -t 86400 cmd
Celui-ci utilise un alarm()
mécanisme semblable à celui d'un utilisateur mais installe un gestionnaire SIGCHLD
(en ignorant les enfants arrêtés) pour détecter l'enfant en train de mourir. Il annule également l’alarme avant son exécution waitpid()
(cela n’annule pas la livraison SIGALRM
si elle était en attente, mais de la façon dont elle est écrite, je ne vois pas cela comme un problème) et tue avant d’ appeler waitpid()
(donc ne peut pas tuer un pid réutilisé.) )
Netpipes a également une timelimit
commande. Celle-ci est antérieure à toutes les autres de plusieurs décennies, adopte une autre approche, mais ne fonctionne pas correctement pour les commandes arrêtées et renvoie un 1
statut de sortie à l'expiration du délai.
Pour répondre plus directement à votre question, vous pouvez faire quelque chose comme:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
C’est-à-dire, vérifiez que le processus est toujours un de nos enfants. Là encore, il y a une petite fenêtre de course (entre ps
récupérer le statut de ce processus et le kill
tuer) au cours de laquelle le processus pourrait mourir et son pid être réutilisé par un autre processus.
Avec quelques coquilles ( zsh
, bash
, mksh
), vous pouvez passer des spécifications d'emploi au lieu de pid.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Cela ne fonctionne que si vous ne créez qu'un seul job d'arrière-plan (sinon, il n'est pas toujours possible d'obtenir la bonne spécification d'emploi de manière fiable).
Si cela pose un problème, démarrez simplement une nouvelle instance de shell:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Cela fonctionne parce que le shell supprime le travail de la table des travaux lors du décès de l'enfant. Dans ce cas, il ne devrait y avoir aucune fenêtre de course car, au moment où le shell appelle kill()
, le signal SIGCHLD n’a pas été traité et le pid ne peut pas être réutilisé (car il n’a pas été attendu), ou il a été traité et le Le travail a été supprimé de la table des processus (et kill
signalerait une erreur). bash
« s kill
au moins des blocs SIGCHLD avant qu'il accède à sa table de travail pour étendre %
et débloque après la kill()
.
Une autre option pour éviter d'avoir ce sleep
processus en suspens même après cmd
sa mort, avec bash
ou ksh93
consiste à utiliser un tuyau avec read -t
au lieu de sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Celui-ci a toujours des conditions de course et vous perdez le statut de sortie de la commande. Il suppose également que cmd
ne ferme pas son fd 4.
Vous pouvez essayer de mettre en œuvre une solution sans race dans perl
les cas suivants:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{CHLD} = sub {
$ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
sigprocmask(SIG_BLOCK, $ss, $oss);
waitpid($p,WNOHANG);
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
unless $? == -1;
sigprocmask(SIG_UNBLOCK, $oss);
};
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
pause while 1;
} else {exec @ARGV}' cmd args...
(bien qu’il faille l’améliorer pour traiter d’autres types d’affaires).
Une autre méthode sans race pourrait utiliser des groupes de processus:
set -m
((sleep 86400; kill 0) & exec cmd)
Cependant, notez que l'utilisation de groupes de processus peut avoir des effets secondaires si des entrées / sorties sont impliquées dans un terminal. Il a cependant l'avantage supplémentaire de tuer tous les autres processus supplémentaires engendrés par cmd
.