Lo mejor sería usar el timeout
comando 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 SIGALRM
entrega entre el waitpid()
regreso y la timeout
salida (cancelando efectivamente esa alarma ). Durante esa pequeña ventana, timeout
incluso 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). timeout
tambié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 perl
como:
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 timelimit
comando en http://devel.ringlet.net/sysutils/timelimit/ (precede a GNU timeout
por 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 SIGALRM
si 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 timelimit
comando. Ese precede a todos los demás por décadas, adopta otro enfoque, pero no funciona correctamente para los comandos detenidos y devuelve un 1
estado 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 ps
recuperar el estado de ese proceso y kill
matarlo) 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 kill
informaría un error). bash
's kill
al 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 sleep
proceso dando vueltas incluso después de que cmd
haya muerto, con bash
o ksh93
es usar una tubería con en read -t
lugar 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 cmd
que 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
.