Najlepiej byłoby użyć timeout
polecenia, jeśli je masz:
timeout 86400 cmd
Obecna implementacja GNU (8.23) działa przynajmniej przy użyciu alarm()
lub równoważnej podczas oczekiwania na proces potomny. Wydaje się, że nie chroni przed SIGALRM
dostarczeniem pomiędzy waitpid()
powrotem a timeout
wyjściem (skuteczne anulowanie tego alarmu ). Podczas tego małego okna timeout
może nawet pisać wiadomości na stderr (na przykład, jeśli dziecko zrzuci rdzeń), co jeszcze bardziej powiększy to okno wyścigu (na czas nieokreślony, jeśli stderr jest na przykład pełną potokiem).
Osobiście mogę żyć z tym ograniczeniem (które prawdopodobnie zostanie naprawione w przyszłej wersji). timeout
dołoży także starań, aby zgłosić poprawny status wyjścia, obsługiwać inne przypadki narożne (takie jak SIGALRM zablokowane / ignorowane przy uruchamianiu, obsługiwać inne sygnały ...) lepiej niż prawdopodobnie robiłbyś to ręcznie.
Dla przybliżenia możesz napisać w następujący sposób 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
Na stronie http://devel.ringlet.net/sysutils/timelimit/ znajduje się timelimit
polecenie (poprzedza GNU o kilka miesięcy).timeout
timelimit -t 86400 cmd
Ten używa alarm()
podobnego mechanizmu, ale instaluje moduł obsługi SIGCHLD
(ignorując zatrzymane dzieci) w celu wykrycia śmierci dziecka. Anuluje również alarm przed uruchomieniem waitpid()
(nie anuluje dostarczenia, SIGALRM
jeśli był w toku, ale sposób, w jaki jest napisany, nie widzę problemu) i zabija przed wywołaniem waitpid()
(więc nie mogę zabić ponownie wykorzystanego pid ).
netpipes ma również timelimit
polecenie. To, że wyprzedza wszystkie pozostałe o dziesięciolecia, przyjmuje jeszcze inne podejście, ale nie działa poprawnie dla zatrzymanych poleceń i zwraca 1
status wyjścia po upływie limitu czasu.
Jako bardziej bezpośrednią odpowiedź na twoje pytanie możesz zrobić coś takiego:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
To znaczy, sprawdź, czy proces jest nadal naszym dzieckiem. Znów jest małe okno wyścigu (pomiędzy ps
odzyskaniem statusu tego procesu a kill
zabiciem go), podczas którego proces może umrzeć, a jego pid może zostać ponownie wykorzystany przez inny proces.
Z niektórych muszli ( zsh
, bash
, mksh
), można przekazać widowisko pracy zamiast PID.
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
Działa to tylko wtedy, gdy spawnujesz tylko jedno zadanie w tle (w przeciwnym razie uzyskanie właściwego rodzaju zadania nie zawsze będzie możliwe w sposób niezawodny).
Jeśli to jest problem, po prostu uruchom nową instancję powłoki:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
Działa to, ponieważ powłoka usuwa zadanie ze stołu zadań po śmierci dziecka. Tutaj nie powinno być żadnego okna wyścigu, ponieważ do czasu wywołania powłoki kill()
albo sygnał SIGCHLD nie został obsłużony, a pid nie mógł zostać ponownie użyty (ponieważ nie był oczekiwany), lub został obsłużony, a zadanie zostało usunięte z tabeli procesów (i kill
zgłosiłoby błąd). bash
„s kill
co najmniej bloków SIGCHLD zanim dostęp swoją tabelę pracy, aby rozwinąć %
i odblokowuje to po kill()
.
Innym rozwiązaniem, aby uniknąć tego sleep
procesu wiszące wokół nawet po cmd
umarł, z bash
lub ksh93
jest użycie rurę read -t
zamiast sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
Ten nadal ma warunki wyścigu i tracisz status wyjścia z polecenia. Zakłada również, cmd
że nie zamyka swojego fd 4.
Możesz spróbować wdrożyć rozwiązanie bez wyścigu w 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...
(choć należałoby go ulepszyć, aby obsługiwał inne typy skrzynek narożnych).
Inną bez rasową metodą może być użycie grup procesów:
set -m
((sleep 86400; kill 0) & exec cmd)
Należy jednak pamiętać, że korzystanie z grup procesów może mieć skutki uboczne, jeśli zaangażowane jest we / wy do urządzenia końcowego. Ma jednak tę dodatkową zaletę, że zabija wszystkie inne dodatkowe procesy odradzane przez cmd
.