Le mieux serait d'utiliser la timeoutcommande 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 SIGALRMlivraison entre le waitpid()retour et la timeoutsortie (ce qui annule effectivement cette alarme ). Pendant cette petite fenêtre, il timeoutest 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). timeoutfera é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 perlcomme 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 timelimitcommande sur http://devel.ringlet.net/sysutils/timelimit/ (devance GNU timeoutde 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 SIGALRMsi 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 timelimitcommande. 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 1statut 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 psrécupérer le statut de ce processus et le killtuer) 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 killsignalerait une erreur). bash« s killau 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 sleepprocessus en suspens même après cmdsa mort, avec bashou ksh93consiste à utiliser un tuyau avec read -tau 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 cmdne ferme pas son fd 4.
Vous pouvez essayer de mettre en œuvre une solution sans race dans perlles 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.