O melhor seria usar o timeout
comando, se você o tiver, para isso:
timeout 86400 cmd
A implementação atual (8.23) GNU pelo menos funciona usando alarm()
ou equivalente enquanto aguarda o processo filho. Parece não proteger contra a SIGALRM
entrega entre waitpid()
retorno e timeout
saída (cancelamento efetivo desse alarme ). Durante essa pequena janela, timeout
pode até escrever mensagens no stderr (por exemplo, se a criança despejar um núcleo), o que aumentaria ainda mais essa janela de corrida (indefinidamente se o stderr for um canal completo, por exemplo).
Pessoalmente, posso conviver com essa limitação (que provavelmente será corrigida em uma versão futura). timeout
também terá um cuidado extra para relatar o status de saída correto, lidar com outros casos de canto (como o SIGALRM bloqueado / ignorado na inicialização, lidar com outros sinais ...) melhor do que você provavelmente conseguiria fazer manualmente.
Como uma aproximação, você pode escrever 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
Existe um timelimit
comando em http://devel.ringlet.net/sysutils/timelimit/ (antecede o GNU timeout
em alguns meses).
timelimit -t 86400 cmd
Esse usa um alarm()
mecanismo semelhante, mas instala um manipulador SIGCHLD
(ignorando crianças paradas) para detectar a morte da criança. Ele também cancela o alarme antes de executar waitpid()
(que não cancela a entrega, SIGALRM
se estava pendente, mas da maneira como está escrito, não vejo problema) e mata antes de ligar waitpid()
(por isso, não é possível matar um pid reutilizado) )
O netpipes também possui um timelimit
comando. Essa é anterior a todas as outras por décadas, adota outra abordagem, mas não funciona corretamente para comandos interrompidos e retorna um 1
status de saída após o tempo limite.
Como resposta mais direta à sua pergunta, você pode fazer algo como:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
Ou seja, verifique se o processo ainda é filho nosso. Novamente, há uma pequena janela de corrida (entre ps
recuperar o status desse processo e kill
matá-lo) durante o qual o processo pode morrer e seu pid ser reutilizado por outro processo.
Com algumas conchas ( zsh
, bash
, mksh
), você pode passar especificações trabalho em vez de PIDs.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Isso só funciona se você gerar apenas um trabalho em segundo plano (caso contrário, nem sempre é possível obter a especificação de trabalho correta).
Se isso é um problema, basta iniciar uma nova instância do shell:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Isso funciona porque o shell remove o trabalho da tabela de trabalhos quando a criança morre. Aqui, não deve haver nenhuma janela de corrida, pois no momento em que o shell chama kill()
, o sinal SIGCHLD não foi tratado e o pid não pode ser reutilizado (pois não foi esperado), ou foi tratado e o sinal o trabalho foi removido da tabela de processos (e kill
reportaria um erro). bash
é kill
, pelo menos, blocos SIGCHLD antes de ele acessa sua tabela de trabalho para expandir o %
e desbloqueia-lo após o kill()
.
Outra opção para evitar a interrupção desse sleep
processo, mesmo após a cmd
morte, com bash
ou ksh93
é usar um cano com, em read -t
vez de sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Aquele ainda tem condições de corrida e você perde o status de saída do comando. Ele também assume cmd
que não fecha seu fd 4.
Você pode tentar implementar uma solução sem raça, perl
como:
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...
(apesar de precisar ser aprimorado para lidar com outros tipos de caixas de canto).
Outro método sem raça pode estar usando grupos de processos:
set -m
((sleep 86400; kill 0) & exec cmd)
No entanto, observe que o uso de grupos de processos pode ter efeitos colaterais se houver E / S em um dispositivo terminal envolvido. Porém, ele tem o benefício adicional de matar todos os outros processos extras gerados cmd
.