Przekrocz limit czasu polecenia w bashu bez niepotrzebnego opóźnienia

283

Ta odpowiedź na polecenie wiersza polecenia, aby automatycznie zabić polecenie po pewnym czasie

proponuje 1-liniową metodę przekroczenia limitu długo działającego polecenia z linii poleceń bash:

( /path/to/slow command with options ) & sleep 5 ; kill $!

Ale możliwe jest, że dane polecenie „długo działające” może zakończyć się przed upływem limitu czasu. (Nazwijmy to „zwykle długo działającym, ale czasem szybkim” poleceniem lub tlrbsf dla zabawy).

Więc to fajne podejście 1-liniowe ma kilka problemów. Po pierwsze, sleepnie jest warunkowy, więc ustawia niepożądaną dolną granicę czasu potrzebnego na zakończenie sekwencji. Rozważ 30s, 2m lub nawet 5m na sen, kiedy komenda tlrbsf kończy się w ciągu 2 sekund - wysoce niepożądane. Po drugie, killjest bezwarunkowe, więc ta sekwencja będzie próbowała zabić niedziałający proces i narzekać na ten temat.

Więc...

Czy istnieje sposób na przekroczenie limitu czasu przez zwykle długo działające, ale czasem szybkie ( „tlrbsf” ) polecenie, które

  • ma implementację bash (inne pytanie ma już odpowiedzi w Perlu i C)
  • zakończy się wcześniejszym z dwóch: zakończenie programu tlrbsf lub upłynął limit czasu
  • nie zabije nieistniejących / niedziałających procesów (lub opcjonalnie: nie narzeka na złe zabicie)
  • nie musi być liniowcem 1-liniowym
  • może działać pod Cygwin lub Linux

... a dla punktów bonusowych uruchamia komendę tlrbsf na pierwszym planie i dowolny proces „uśpienia” lub dodatkowy proces w tle, tak że stdin / stdout / stderr komendy tlrbsf może zostać przekierowany, tak jakby był uruchomić bezpośrednio?

Jeśli tak, udostępnij swój kod. Jeśli nie, proszę wyjaśnić dlaczego.

Spędziłem trochę czasu próbując zhakować powyższy przykład, ale osiągam limit moich umiejętności bashowych.

system PAUZA
źródło
5
Kolejne podobne pytanie: stackoverflow.com/questions/526782/... (ale myślę, że odpowiedź „timeout3” tutaj jest znacznie lepsza).
system PAUZA
2
Czy jest jakiś powód, aby nie korzystać z timeoutnarzędzia GNU ?
Chris Johnson
timeoutjest świetne! możesz nawet używać wielu poleceń (skrypt wieloliniowy): stackoverflow.com/a/61888916/658497
Noam Manos

Odpowiedzi:

149

Myślę, że właśnie o to prosisz:

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $$ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $$ && kill -0 $$ || exit 0
    sleep $delay
    kill -s SIGKILL $$
) 2> /dev/null &

exec "$@"
Juliano
źródło
To ciekawa sztuczka, polegająca na użyciu $$ jako części tła. I fajnie, że natychmiast zabije zawieszony tlrbsf. Ale, ugh, musisz wybrać interwał sondowania. A jeśli ustawisz zbyt niski poziom odpytywania, zje on procesor ze stałą sygnalizacją, dzięki czemu tlrbsf działa jeszcze dłużej!
system PAUZA
7
Nie musisz wybierać interwału odpytywania, domyślnie jest to 1s, co jest całkiem niezłe. A sprawdzenie jest bardzo tanie, koszty ogólne są znikome. Wątpię, by to spowodowało, że tlrbsf działałby zauważalnie dłużej. Testowałem ze snem 30 i dostałem różnicę 0,000 ms między używaniem a nieużywaniem.
Juliano
6
Racja, teraz to widzę. I spełnia moje dokładne wymagania, jeśli ustawisz interwał sondowania == limit czasu. Działa również w potokach, działa z całym tłem, działa z wieloma instancjami i innymi uruchomionymi zadaniami. Kochanie, dzięki!
system PAUZA
Wysłanie sygnału zabija podpowłokę, więc pomyślałem, że umieszczenie wszystkich poleceń zabicia w jednej linii je zachowa. Włączyłem również wyjście stderr, aby wyświetlało nieoczekiwane błędy. stackoverflow.com/questions/687948/...
węgorz ghEEz
1
@Juliano To świetny sposób radzenia sobie z limitami czasu, bardzo przydatny. Zastanawiam się, czy istnieje sposób, w jaki skrypt może zwrócić kod wyjścia 143, gdy proces zostanie zabity po upływie limitu czasu? Próbowałem dodać „exit 143” zaraz po poleceniu kill, ale zawsze otrzymuję kod wyjścia 0 w skrypcie wywołującym.
Salman A. Kagzi
528

Prawdopodobnie szukasz timeoutpolecenia w coreutils. Ponieważ jest częścią coreutils, jest technicznie rozwiązaniem typu C, ale nadal jest coreutils. info timeoutpo więcej szczegółów. Oto przykład:

timeout 5 /path/to/slow/command with options
jęknął
źródło
21
W systemie Mac możesz zainstalować to za pośrednictwem Macports lub homebrew.
Ivan Z. Siu
23
Po zainstalowaniu przez homebrew na OS X polecenie staje sięgtimeout
ethicalhack3r
5
... jakiego używasz systemu operacyjnego, który ma Coreutils sprzed 2003 roku?
Keith
5
@Keith: CentOS 5.10, na przykład :-(
Wstrzymano do odwołania.
9
Aby to wyjaśnić, potrzebna jest komenda homebrew na OSX / mac brew install coreutils, a następnie można jej użyć gtimeout.
Ohad Schneider
37

To rozwiązanie działa niezależnie od trybu monitorowania bash. Możesz użyć odpowiedniego sygnału, aby zakończyć polecenie

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

Obserwator zabija twoją komendę po upływie określonego czasu; skrypt czeka na wolne zadanie i kończy obserwatora. Zauważ, że waitnie działa z procesami, które są dziećmi innej powłoki.

Przykłady:

  • twoja_komenda działa dłużej niż 2 sekundy i została zakończona

twoje polecenie zostało przerwane

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • twoja_komenda zakończyła się przed upływem limitu czasu (20 sekund)

twoja_komenda zakończona

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
Dmitry
źródło
1
waitzwraca status zakończenia procesu, na który czeka. Więc jeśli twoje polecenie zakończy działanie w wyznaczonym czasie, ale z niezerowym statusem wyjścia, wówczas logika będzie zachowywać się tak, jak upłynął limit czasu, tj your_command interrupted. Drukuj . Zamiast tego mógłby zrobić to waitbez przeprowadzenia if, a następnie sprawdzić, czy $watcherPID nadal istnieje, jeśli tak, to wiesz, że nie zrobił limitu czasu.
George Hawkins,
Nie mogę zrozumieć, dlaczego tak się killdzieje w jednym przypadku, ale pkillw drugim. Musiałem użyć pkillobu, aby to działało poprawnie. Spodziewałbym się, że jeśli zapiszesz polecenie (), będziesz musiał użyć go, pkillaby je zabić. Ale może działa to inaczej, jeśli wewnątrz jest tylko jedno polecenie ().
nobar
Niech Bóg was błogosławi :)
Darko Miletic,
22

Proszę bardzo:

timeout --signal=SIGINT 10 /path/to/slow command with options

możesz zmienić SIGINTi 10jak chcesz;)

Aarian P. Aleahmad
źródło
3
„limit czasu” jest częścią pakietu coreutils na (przynajmniej) Redhat, Centos, Suse i Ubuntu, więc musisz go zainstalować, jeśli go nie masz.
Akom
to jest naprawdę pomocne !!!!!! Czy wiesz, dlaczego „limit czasu 5 / ścieżka / do / zwolnienia / polecenia z opcjami” yingted czasami nie działa?
Decula,
Niestety nie ma tego w coreutilspakiecie na FreeBSD.
Paul Bissex
18

Możesz to zrobić całkowicie za pomocą bash 4.3i powyżej:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Przykład: _timeout 5 longrunning_command args
  • Przykład: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • Przykład: producer | { _timeout 5 consumer1; consumer2; }
  • Przykład: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Potrzebuje Bash 4.3 dla wait -n

  • Daje 137, jeśli polecenie zostało zabite, w przeciwnym razie zwraca wartość polecenia.
  • Działa na rury. (Nie musisz tutaj iść na pierwszy plan!)
  • Działa również z wewnętrznymi poleceniami lub funkcjami powłoki.
  • Działa w podpowłoce, więc przepraszamy, nie eksportujemy żadnej zmiennej do bieżącej powłoki.

Jeśli nie potrzebujesz kodu powrotu, możesz to uczynić jeszcze prostszym:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Uwagi:

  • Ściśle mówiąc nie potrzebne ;w ; ), jednak czyni rzeczy bardziej spójne z ; }-case. I set +bprawdopodobnie można go również zostawić, ale lepiej zabezpieczyć niż przepraszać.

  • Z wyjątkiem --forground(prawdopodobnie) możesz zaimplementować timeoutobsługę wszystkich wariantów . --preserve-statusjest jednak trochę trudny. Pozostaje to jako ćwiczenie dla czytelnika;)

Ten przepis można zastosować „naturalnie” w skorupce (tak naturalnej jak dla flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

Jednak, jak wyjaśniono powyżej, nie można w ten sposób ponownie eksportować zmiennych środowiskowych do otaczającej powłoki w ten sposób.

Edytować:

Przykład ze świata rzeczywistego: Limit czasu __git_ps1w przypadku, gdy zajmuje to zbyt dużo czasu (np. Powolne łącza SSHFS):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edycja2: Bugfix. Zauważyłem, że exit 137nie jest to konieczne, a jednocześnie _timeoutzawodne.

Edycja3: gitjest bardzo trudna, więc potrzebuje podwójnej sztuczki, aby działać satysfakcjonująco.

Edit4: Zapomniałem o _pierwszym _timeoutdla prawdziwego przykładu GIT.

Tino
źródło
1
Uderz 4 skały. To wszystko.
system PAUZA
1
To wymaga Bash 4.3 lub nowszej wersji. cc. The 'wait' builtin has a new '-n' option to wait for the next child to change status. Od: tiswww.case.edu/php/chet/bash/NEWS
Ben Reser
17

Wolę „timelimit”, który ma pakiet przynajmniej w debianie.

http://devel.ringlet.net/sysutils/timelimit/

Jest nieco ładniejszy niż „limit czasu” coreutils, ponieważ drukuje coś po zabiciu procesu, a także domyślnie wysyła SIGKILL po pewnym czasie.

maxy
źródło
Wygląda na to, że nie działa całkiem dobrze: / $ timelimit -T2 sleep 10 real 0m10.003s user 0m0.000s sys 0m0.000s
hithwen
3
Użyj -t2 nie -T2. Duże -T to czas od wysłania SIGTERM do wysłania SIGKILL.
maksy
1
Chciałbym dodać, że timelimit 1.8 nie wydaje się dobrze współpracować z fork ( timelimit -t1 ./a_forking_progzabij tylko jeden z dwóch procesów), ale limit czasu działa.
Jeremy Cochoy
Jeśli chcesz, aby limit czasu został wydrukowany podczas zabijania procesu, użyj flagi „-v”.
10

Aby przekroczyć limit czasu slowcommandpo 1 sekundzie:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

Aby ustalić, czy polecenie przekroczyło limit czasu, czy zakończyło się niepowodzeniem z własnych powodów, sprawdź, czy kod stanu to 124:

# ping for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

Zauważ, że gdy status wyjścia wynosi 124, nie wiesz, czy upłynął timeoutlimit czasu z powodu twojego polecenia, czy też samo polecenie zostało zakończone z powodu własnej wewnętrznej logiki przekroczenia limitu czasu, a następnie zwróciło 124. Możesz bezpiecznie założyć w obu przypadkach jednak nastąpił pewien czas.

lance.dolan
źródło
9

Zobacz także http://www.pixelbeat.org/scripts/timeout skrypt, którego funkcjonalność została zintegrowana z nowszymi coreutils

pixelbeat
źródło
Zgrabny, prosty, wykorzystuje TERM, a nie KILL. Miły! Kiedy pierwotnie zadawałem pytanie, szukałem takiego rozwiązania typu pułapka / oczekiwanie.
system PAUSE
limit czasu zwraca 124 w przypadku zabicia.
ceving
8

Trochę hacky, ale działa. Nie działa, jeśli masz inne procesy na pierwszym planie (pomóż mi to naprawić!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

Myślę, że można to odwrócić, spełniając kryteria „bonusu”:

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
strager
źródło
(ls -ltR / cygdrive / c / windows & SPID = $ {!}; (sleep 1s; kill $ {SPID}) & CPID = $ {!}; fg 1; kill $ {CPID})> fdsa
system PAUSE
bash: fg: brak kontroli zadań
system PAUZA
@system PAUSE, set -m, myślę.
strager
Mam kontrolę zadań (zestaw -m) w powłoce logowania. To jest „m” w treści himBH $ -, ale wydaje się, że znika dla podpowłoki. Prawdopodobnie artefakt Cygwina. grumble
system PAUZA
Nie używaj „fg” w skryptach. Przeczytaj „pomoc w oczekiwaniu”.
lhunath,
8

limit czasu jest prawdopodobnie pierwszym podejściem do wypróbowania. W przypadku przekroczenia limitu czasu może być konieczne powiadomienie lub inne polecenie. Po kilku poszukiwaniach i eksperymentach wymyśliłem ten skrypt bash :

if 
    timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
    timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
    echo 'OK'; #if you want a positive response
else
    echo 'Not OK';
    AND_ALTERNATIVE_COMMANDS
fi
użytkownik2099484
źródło
5

Prosty skrypt z przejrzystością kodu. Zapisz w /usr/local/bin/run:

#!/bin/bash

# run
# Run command with timeout $1 seconds.

# Timeout seconds
timeout_seconds="$1"
shift

# PID
pid=$$

# Start timeout
(
  sleep "$timeout_seconds"
  echo "Timed out after $timeout_seconds seconds"
  kill -- -$pid &>/dev/null
) &
timeout_pid=$!

# Run
"$@"

# Stop timeout
kill $timeout_pid &>/dev/null

Przekroczono limit czasu polecenia, które działa zbyt długo:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

Kończy się natychmiast po wykonaniu polecenia:

$ run 10 sleep 2
$

źródło
3

Jeśli znasz już nazwę programu (załóżmy program), aby zakończył się po upływie limitu czasu (jako przykładowe 3sekundy), mogę wnieść proste i nieco brudne alternatywne rozwiązanie:

(sleep 3 && killall program) & ./program

Działa to doskonale, jeśli nazywam procesy testowe wywołaniami systemowymi.

loup
źródło
2
To zabija inne procesy, które wykorzystują nazwę i nie zabija procesu o podanej nazwie, jeśli zmieni nazwę (np. Pisząc do argv[0], być może z innymi hackami, aby zrobić więcej miejsca).
Jed
Znalazłem odmianę tego przydatnego przy próbie zatrzymania kontenerów dokerów po pewnym czasie. Wygląda na to, że klient dokera nie akceptuje TERM / INT / KILL w sposób, który faktycznie zatrzymywałby kontener uruchamiany na pierwszym planie. Nadanie kontenerowi nazwy i użycie (sleep 3 && docker stop <container>) &działało ładnie. Dzięki!
Mat Schaffer,
tak, jest brudny i ograniczony, ale można go ulepszyć w następujący { sleep 5 && kill -9 $(ps -fe | grep "program" | grep $$ | tr -s " " | cut -d" " -f2); } & SLEEPPID=$!; bash -c "program" && kill -9 $SLEEPPID"sposób : W ten sposób zabije tylko zadania w bieżącej powłoce.
ton
2

Jest też cratimeoutMartin Cracauer (napisany w C dla systemów Unix i Linux).

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
max32
źródło
„W szczególności zachowuje zachowanie sygnału”. Miło mieć tę opcję!
system PAUZA
1

OS X nie używa jeszcze bash 4, nie ma też / usr / bin / timeout, więc oto funkcja, która działa na OS X bez home-brew lub macports, podobna do / usr / bin / timeout (na podstawie Tino's odpowiedź). Sprawdzanie poprawności parametrów, pomoc, użycie i obsługa innych sygnałów są ćwiczeniem dla czytelnika.

# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
    set -m +b
    sleep "$1" &
    SPID=${!}
    ("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
    CPID=${!}
    wait %1
    SLEEPRETVAL=$?
    if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
      RETVAL=124
      # When you need to make sure it dies
      #(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
      wait %2
    else
      wait %2
      RETVAL=$?
    fi
    return $RETVAL
) }
NerdMachine
źródło
0

W 99% przypadków odpowiedzią NIE jest implementacja logiki przekroczenia limitu czasu. Logika Timeout jest w prawie każdej sytuacji czerwony znak ostrzegawczy, że coś innego jest nie tak i należy ustalić zamiast .

Czy czasami proces zawiesza się lub przerywa po n sekundach? Następnie dowiedz się, dlaczego i napraw to.

Nawiasem mówiąc, aby poprawnie rozwiązać problem z funkcją stragera, musisz użyć czekania „$ SPID” zamiast fg 1, ponieważ w skryptach nie masz kontroli zadań (a próba włączenia go jest głupia). Co więcej, fg 1 polega na tym, że nie zacząłeś wcześniej żadnych innych zadań w skrypcie, co jest złym założeniem.

lhunath
źródło
4
Mając dostęp do 100% źródła (i większości urządzeń, takich jak przełączniki sieciowe), zgodziłbym się, że prawdopodobnie są lepsze rozwiązania niż przekroczenie limitu czasu. Ale kiedy „tlrbsf” ma źródło zamknięte, tylko binarne, czasami trzeba obejść to ograniczenie.
system PAUZA
@lhunath, „w skryptach nie masz kontroli zadań (a próba włączenia jest głupia)” - Proszę wyjaśnić tutaj: stackoverflow.com/questions/690266/…
PAUZA systemowa
@sUSE PAUSE: Odpowiedź stackoverflow.com/questions/690266/... jest poprawna, również to skomentowałem.
lhunath,
29
Lhunath, to, co mówisz, nie ma sensu. istnieje mnóstwo przypadków, w których przekroczenie limitu czasu jest dobrym rozwiązaniem, np. za każdym razem, gdy musisz przejść przez sieć.
Nate Murray
Wyjątek: piszesz skrypty testowe, aby sprawdzić, czy już uruchomione oprogramowanie nie ma problemu z przekroczeniem limitu czasu.
peterh - Przywróć Monikę
0

Wystąpił problem z zachowaniem kontekstu powłoki i umożliwieniem przekroczenia limitu czasu, jedynym problemem jest to, że zatrzyma on wykonywanie skryptu po przekroczeniu limitu czasu - ale jest to zgodne z potrzebami, które mi przedstawiono:

#!/usr/bin/env bash

safe_kill()
{
  ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}

my_timeout()
{
  typeset _my_timeout _waiter_pid _return
  _my_timeout=$1
  echo "Timeout($_my_timeout) running: $*"
  shift
  (
    trap "return 0" USR1
    sleep $_my_timeout
    echo "Timeout($_my_timeout) reached for: $*"
    safe_kill $$
  ) &
  _waiter_pid=$!
  "$@" || _return=$?
  safe_kill $_waiter_pid -USR1
  echo "Timeout($_my_timeout) ran: $*"
  return ${_return:-0}
}

my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true  && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd

z wyjściami:

Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated

oczywiście zakładam, że istniał reż scripts

mpapis
źródło
0
#! /bin/bash
timeout=10
interval=1
delay=3
(
    ((t = timeout)) || :

    while ((t > 0)); do
        echo "$t"
        sleep $interval
        # Check if the process still exists.
        kill -0 $$ 2> /dev/null || exit 0
        ((t -= interval)) || :
    done

    # Be nice, post SIGTERM first.
    { echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &

exec "$@"
węgorz ghEEz
źródło
@Tino Przepraszam, zapomniałem, dlaczego zmieniłem linię zakończenia procesu i dlaczego uważałem, że to ważne, aby udostępnić. Szkoda, że ​​tego nie zapisałem. Być może stwierdziłem, że muszę się zatrzymać, zanim sprawdzę, czy TERM został zabity. Skrypt z 2008 roku z książki kucharskiej wydaje się sprawdzać stan procesu natychmiast po wysłaniu SIGTERM, co może prowadzić do błędu podczas próby wysłania SIGKILL do procesu, który zmarł.
węgorz ghEEz
0

Mój problem był może trochę inny: uruchamiam polecenie przez ssh na zdalnej maszynie i chcę zabić powłokę i dziecko, jeśli polecenie się zawiesi.

Teraz używam następujących:

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

W ten sposób polecenie zwraca 255, gdy upłynął limit czasu lub kod powrotu polecenia w przypadku powodzenia

Należy pamiętać, że procesy zabijania z sesji ssh są obsługiwane inaczej niż interaktywna powłoka. Ale możesz również użyć opcji -t, aby ssh przydzielić pseudo terminal, więc działa on jak interaktywna powłoka

Franky_GT
źródło
0

Oto wersja, która nie polega na spawnowaniu procesu potomnego - potrzebowałem samodzielnego skryptu, który osadził tę funkcjonalność. Wykonuje również ułamkowy interwał odpytywania, dzięki czemu można odpytywać szybciej. limit czasu byłby preferowany - ale utknąłem na starym serwerze

# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
    local timeout=$1; shift
    local interval=$1; shift
    $* &
    local child=$!

    loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
    ((t = loops))
    while ((t > 0)); do
        sleep $interval
        kill -0 $child &>/dev/null || return
        ((t -= 1))
    done

    kill $child &>/dev/null || kill -0 $child &>/dev/null || return
    sleep $interval
    kill -9 $child &>/dev/null
    echo Timed out
}

slow_command()
{
    sleep 2
    echo Completed normally
}

# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command

# or call an external command
wait_on_command 1 0.1 sleep 10
Neil McGill
źródło
-1

Bardzo uproszczony sposób:

# command & sleep 5; pkill -9 -x -f "command"

przy pomocy pkill (opcja -f ) możesz zabić swoje polecenie za pomocą argumentów lub podać -n, aby uniknąć zabicia starego procesu.

Francis
źródło
Zdajesz sobie sprawę, że to właśnie OP ma w swoim poście i co wskazuje, że nie chce, prawda? Ponieważ zawsze czeka pełne opóźnienie snu.
Etan Reisner,
-1

Opierając się na odpowiedzi @ loup ...

Jeśli chcesz przekroczyć limit czasu procesu i wyciszyć wyjście kill / pid, uruchom:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

Powoduje to umieszczenie procesu w tle w podpowłoce, dzięki czemu nie widać wyników zadania.

Cody A. Ray
źródło
-3

Mam zadanie crona, które wywołuje skrypt php i czasami utknie w skrypcie php. To rozwiązanie było dla mnie idealne.

Używam:

scripttimeout -t 60 /script.php
Jehad Buria
źródło
1
Co to jest scripttimeout?
rudolfbyker