Лучше всего использовать timeoutкоманду, если она у вас есть:
timeout 86400 cmd
Текущая реализация (8.23) GNU по крайней мере работает с использованием alarm()или эквивалентным образом, ожидая дочерний процесс. Похоже, он не защищает от SIGALRMдоставки между waitpid()возвращением и timeoutвыходом (фактически отменяя эту тревогу ). В течение этого небольшого окна timeoutможет даже писать сообщения на stderr (например, если дочерний объект сбросил ядро), что еще больше увеличит это окно гонки (на неопределенное время, если, например, stderr - полный канал).
Я лично могу жить с этим ограничением (которое, вероятно, будет исправлено в будущей версии). timeoutбудет также уделять особое внимание сообщению о правильном состоянии выхода, обрабатывать другие угловые случаи (например, SIGALRM блокируется / игнорируется при запуске, обрабатывать другие сигналы ...) лучше, чем вы, вероятно, сможете сделать вручную.
В качестве приближения вы можете написать это perlтак:
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
На http://devel.ringlet.net/sysutils/timelimit/ есть timelimitкоманда (предшествующая GNU на несколько месяцев).timeout
timelimit -t 86400 cmd
Тот использует alarm()механизм, похожий на другой, но устанавливает обработчик SIGCHLD(игнорируя остановленных потомков), чтобы обнаружить умирающего ребенка. Он также отменяет сигнал тревоги перед запуском waitpid()(это не отменяет доставку, SIGALRMесли она ожидала, но так, как написано, я не вижу в этом проблемы) и убивает перед вызовом waitpid()(поэтому не может убить повторно использованный pid ).
У netpipes также есть timelimitкоманда. Тот, кто предшествует всем остальным на десятилетия, использует еще один подход, но не работает должным образом для остановленных команд и возвращает состояние 1выхода по истечении времени ожидания.
Как более прямой ответ на ваш вопрос, вы можете сделать что-то вроде:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
То есть проверьте, что этот процесс все еще является нашим ребенком. Опять же, есть небольшое окно гонки (между psполучением статуса этого процесса и killего уничтожением), во время которого процесс может умереть и его pid может быть использован другим процессом.
С некоторыми оболочками ( zsh, bash, mksh), вы можете передать функции задания вместо ИДП.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Это работает только в том случае, если вы создаете только одну фоновую работу (в противном случае получение правильной спецификации задания не всегда возможно надежно).
Если это проблема, просто запустите новый экземпляр оболочки:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Это работает, потому что оболочка удаляет задание из таблицы заданий после смерти ребенка. Здесь не должно быть никакого гоночного окна, так как к тому времени, когда оболочка вызывает kill(), либо сигнал SIGCHLD не был обработан, и pid не может быть повторно использован (так как он не ожидался), либо он был обработан и задание было удалено из таблицы процессов (и killбудет сообщать об ошибке). bash«S по killкрайней мере блоков SIGCHLD , прежде чем он получает доступ к таблице заданий , чтобы расширить %и разблокирует его после kill().
Еще один вариант, чтобы избежать sleepзависания этого процесса даже после cmdего смерти, с помощью bashили ksh93использовать трубу read -tвместо sleep:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
У этого все еще есть условия гонки, и вы теряете статус выхода команды. Это также предполагает, cmdчто не закрывает свой fd 4.
Вы можете попробовать внедрить решение без гонок, 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...
(хотя это должно было бы быть улучшено, чтобы обращаться с другими типами угловых случаев).
Другим методом без гонки может быть использование групп процессов:
set -m
((sleep 86400; kill 0) & exec cmd)
Тем не менее, обратите внимание, что использование групп процессов может иметь побочные эффекты, если имеется ввод-вывод для терминального устройства. Это имеет дополнительное преимущество, хотя убивает все другие дополнительные процессы, порожденные cmd.