Ekwiwalent POSIX dla limitu czasu GNU?

14

Komenda GNU coreutils timeoutjest niezwykle przydatna w niektórych sytuacjach skryptowych, pozwalając na użycie danych wyjściowych polecenia, jeśli jest ono szybkie, i pomijanie, jeśli zajmie to zbyt dużo czasu.

Jak mogę przybliżyć podstawowe zachowanie timeoutprzy użyciu tylko narzędzi określonych w POSIX?


(Myślę, że może to wiązać się z kombinacji wait, sleep, killi kto wie co jeszcze, ale być może brakuje mi łatwiejszy podejście).

Dzika karta
źródło
3
Zobacz Przekroczanie limitu czasu w skrypcie powłoki , ale nie uważam tego za duplikat, ponieważ zażądałem przenośności do systemów wcześniejszych niż POSIX i wymagałem zachowania stdin i stdout, które wykluczają niektóre rozwiązania obejmujące procesy w tle.
Gilles „SO- przestań być zły”
command & pid=$! ; sleep 5 && kill $pid
Pandya,
1
@ Pandya nie wprowadza niewielkiego warunku wyścigu, w którym jeśli commandzakończy się szybko, istnieje niewielka szansa na ponowne pidużycie przez inny proces przed uruchomieniem killpolecenia? Nie chciałbym tego w kodzie produkcyjnym ....
Wildcard,
Czy możesz wyjaśnić więcej na temat problemu, który próbujesz rozwiązać? Dlaczego nie skompilować timeoutprogramu i go użyć?
James Youngman,
2
@JamesYoungman, zgaduję, że nie znasz się na pisaniu skryptów w celu przenoszenia . Pytanie o sposób robienia czegoś zgodny z POSIX (lub określony przez POSIX) oznacza , że ma on być przenośny. Kompilowanie kodu źródłowego w plik binarny jest zdecydowanie nieprzenośne i, w zależności od zasad bezpieczeństwa firmy, kompilator może nie być zainstalowany na serwerze produkcyjnym.
Wildcard

Odpowiedzi:

2

Moje podejście byłoby takie:

  • Wykonaj polecenie jako proces w tle 1

  • Uruchom „timer nadzorujący” jako proces w tle 2

  • Skonfiguruj moduł obsługi, aby przechwytywał sygnał zakończenia w powłoce nadrzędnej

  • Poczekaj na zakończenie obu procesów. Proces, który kończy się jako pierwszy, wysyła sygnał zakończenia do rodzica.

  • Moduł obsługi pułapek rodzica zabija oba procesy w tle poprzez kontrolę zadań (jeden z nich został już z definicji zakończony, ale to zabicie będzie nieszkodliwym brakiem operacji, ponieważ nie używamy PID, patrz poniżej)

Próbowałem ominąć możliwy warunek wyścigu opisany w komentarzach, używając identyfikatorów kontroli zadań powłoki (które byłyby jednoznaczne w obrębie tej instancji powłoki), aby zidentyfikować procesy w tle do zabicia, zamiast systemowych PID.

#!/bin/sh

TIMEOUT=$1
COMMAND='sleep 5'

function cleanup {
    echo "SIGTERM trap"
    kill %1 %2
}

trap cleanup SIGTERM

($COMMAND; echo "Command completed"; kill $$) &
(sleep $TIMEOUT; echo "Timeout expired"; kill $$) &

wait
echo "End of execution"

Wynik dla TIMEOUT=10(polecenie kończy się przed watchdog):

$ ./timeout.sh 10
Command completed
SIGTERM trap
End of execution

Wynik dla TIMEOUT=1(watchdog kończy się przed poleceniem):

$ ./timeout.sh 1
Timeout expired
SIGTERM trap
End of execution

Wynik dla TIMEOUT=5(watchdog i polecenie kończą „prawie” jednocześnie):

./tst.sh 5
Timeout expired
Command completed
SIGTERM trap
End of execution
Guido
źródło
Nie jest to zgodne z POSIX, ponieważ (a) definicja funkcji powinna być cleanup() { ...; }i (b) kontrola zadań jest funkcją bash nieokreśloną przez POSIX (chociaż ksh i inni też ją mają). Niezły skrypt!
Wildcard
Możesz także lepiej radzić sobie z przeplataniem listy argumentów timeout="$1"; shifti (exec "$@"; echo "Command completed"; kill $$)zamiast jej zmieniać.
Wildcard