Melhor seria usar o comando timeout
se você o tiver feito, o que significa:
timeout 86400 cmd
A implementação atual (8.23) do GNU funciona pelo menos usando alarm()
ou equivalente enquanto aguarda o processo filho. Ele não parece estar protegendo contra o SIGALRM
sendo entregue entre waitpid()
retornando e timeout
saindo (efetivamente cancelando esse 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 a janela de corrida (indefinidamente se stderr é um tubo cheio, por exemplo).
Eu pessoalmente posso viver com essa limitação (que provavelmente será corrigida em uma versão futura). timeout
também tomará cuidado extra para informar o status de saída correto, lidar com outros casos de canto (como SIGALRM bloqueado / ignorado na inicialização, lidar com outros sinais ...) melhor do que você provavelmente conseguiria fazer à mão.
Como uma aproximação, você pode escrever em 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
Há um comando timelimit
no link (precede o GNU timeout
em alguns meses).
timelimit -t 86400 cmd
Esse usa um mecanismo alarm()
-like, mas instala um manipulador em SIGCHLD
(ignorando filhos interrompidos) para detectar a criança morrendo. Ele também cancela o alarme antes de executar waitpid()
(isso não cancela a entrega de SIGALRM
se ele estava pendente, mas a maneira como está escrito, não consigo ver como é um problema) e mata antes chamando waitpid()
(então não pode matar um pid reutilizado).
netpipes também tem um comando timelimit
. Aquele que antecede todos os outros por décadas, leva ainda uma outra abordagem, mas não funciona corretamente para comandos interrompidos e retorna um status de saída 1
no tempo limite.
Como uma resposta mais direta à sua pergunta, você pode fazer algo como:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
Isto é, verifique se o processo ainda é um filho nosso. Novamente, há uma pequena janela de corrida (entre ps
recuperando o status desse processo e kill
matando) durante a qual o processo pode morrer e seu pid ser reutilizado por outro processo.
Com alguns shells ( zsh
, bash
, mksh
), você pode passar especificações do job 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, obter o jobpec correto nem sempre é possível de forma confiável).
Se isso for 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 tarefas quando a criança está morrendo. Aqui, não deve haver nenhuma janela de corrida, pois quando o shell chamar kill()
, o sinal SIGCHLD não foi tratado e o pid não pode ser reutilizado (já que não foi aguardado), ou foi manipulado e o trabalho foi removido da tabela de processos (e kill
reportaria um erro). bash
kill
pelo menos bloqueia SIGCHLD antes de acessar sua tabela de trabalhos para expandir o %
e desbloqueá-lo após o kill()
.
Outra opção para evitar que o processo sleep
permaneça, mesmo depois que cmd
morreu, com bash
ou ksh93
é usar um canal com read -t
em vez de sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Esse ainda tem condições de corrida e você perde o status de saída do comando. Também assume que cmd
não fecha seu fd4.
Você poderia tentar implementar uma solução sem raça em 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...
(embora seja necessário melhorar para lidar com outros tipos de casos de canto).
Outro método sem raça poderia 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 de terminal envolvido. Ele tem o benefício adicional de matar todos os outros processos extras gerados por cmd
.