Лучше всего использовать 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
.