Lo mejor sería usar el timeoutcomando si lo tiene, que está destinado para eso:
timeout 86400 cmd
La implementación actual de GNU (8.23) al menos funciona utilizando alarm()o equivalente mientras espera el proceso secundario. No parece estar protegiendo contra la SIGALRMentrega entre el waitpid()regreso y la timeoutsalida (cancelando efectivamente esa alarma ). Durante esa pequeña ventana, timeoutincluso puede escribir mensajes en stderr (por ejemplo, si el niño arrojó un núcleo), lo que agrandaría aún más esa ventana de carrera (indefinidamente si stderr es una tubería completa, por ejemplo).
Personalmente, puedo vivir con esa limitación (que probablemente se solucionará en una versión futura). timeouttambién tendrá mucho cuidado de informar el estado de salida correcto, manejar otros casos de esquina (como SIGALRM bloqueado / ignorado en el inicio, manejar otras señales ...) mejor de lo que probablemente podría hacer a mano.
Como aproximación, podría escribirlo perlcomo:
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
Hay un timelimitcomando en http://devel.ringlet.net/sysutils/timelimit/ (precede a GNU timeoutpor unos meses).
timelimit -t 86400 cmd
Ese usa un alarm()mecanismo similar pero instala un controlador SIGCHLD(ignorando a los niños detenidos) para detectar la muerte del niño. También cancela la alarma antes de ejecutar waitpid()(eso no cancela la entrega de SIGALRMsi estaba pendiente, pero por la forma en que está escrito, no puedo ver que sea un problema) y mata antes de llamar waitpid()(así que no puedo matar un pid reutilizado )
netpipes también tiene un timelimitcomando. Ese precede a todos los demás por décadas, adopta otro enfoque, pero no funciona correctamente para los comandos detenidos y devuelve un 1estado de salida al finalizar el tiempo de espera.
Como respuesta más directa a su pregunta, puede hacer algo como:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
Es decir, verifique que el proceso todavía sea un hijo nuestro. Nuevamente, hay una pequeña ventana de carrera (entre psrecuperar el estado de ese proceso y killmatarlo) durante el cual el proceso podría morir y su pid será reutilizado por otro proceso.
Con algunas conchas ( zsh, bash, mksh), puede pasar especificaciones de trabajo en lugar de los PID.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Eso solo funciona si genera un solo trabajo en segundo plano (de lo contrario, obtener la especificación de trabajo correcta no siempre es posible de manera confiable).
Si eso es un problema, simplemente inicie una nueva instancia de shell:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Eso funciona porque el shell elimina el trabajo de la tabla de trabajos cuando el niño muere. Aquí, no debería haber ninguna ventana de carrera ya que para cuando el shell llama kill(), o la señal SIGCHLD no se ha manejado y el pid no se puede reutilizar (ya que no se ha esperado), o se ha manejado y el el trabajo se ha eliminado de la tabla de procesos (e killinformaría un error). bash's killal menos bloquea SIGCHLD antes de acceder a su tabla de trabajo para expandirlo %y desbloquearlo después de kill().
Otra opción para evitar tener ese sleepproceso dando vueltas incluso después de que cmdhaya muerto, con basho ksh93es usar una tubería con en read -tlugar de sleep:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Ese todavía tiene condiciones de carrera, y pierdes el estado de salida del comando. También supone cmdque no cierra su fd 4.
Podrías intentar implementar una solución libre de carrera en perl:
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...
(aunque necesitaría ser mejorado para manejar otros tipos de casos de esquina).
Otro método sin raza podría ser el uso de grupos de procesos:
set -m
((sleep 86400; kill 0) & exec cmd)
Sin embargo, tenga en cuenta que el uso de grupos de procesos puede tener efectos secundarios si hay E / S en un dispositivo terminal involucrado. Sin embargo, tiene el beneficio adicional de matar todos los demás procesos adicionales generados por cmd.