Найкраще було б використовувати 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
Існує timelimit
команда на веб- сайті http://devel.ringlet.net/sysutils/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
вбити його), під час якого процес може загинути, а його під буде повторно використаний іншим процесом.
З деякими оболонками ( 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
.