Jak napisać logikę ponownej próby w skrypcie, aby kontynuować próbę uruchomienia go do 5 razy?

112

Chcę napisać logikę w skrypcie powłoki, który spróbuje uruchomić go ponownie po 15 sekundach do 5 razy w oparciu o „kod statusu = FAIL”, jeśli nie powiedzie się z powodu jakiegoś problemu.

Sandeep Singh
źródło

Odpowiedzi:

90

Ten skrypt używa licznika, naby ograniczyć liczbę prób wykonania polecenia do pięciu. Jeśli polecenie zakończy się powodzeniem, $?utrzyma zero i wykonanie przerwie się w pętli.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done
podejrzenie
źródło
1
powinieneś dodać, breakże jeśli komenda się powiedzie, to przerwie to pętlę
Rahul Patil
Właściwie właściwy sposób pisania, który jest, if command; then break; fia ściślej command && break
mówiąc,
1
„polecenie” to tylko nazwa polecenia, którego status chcesz sprawdzić.
podejrzany
3
Warto zauważyć, że możesz sprawdzić, czy n jest na końcu pięć, aby wiedzieć, czy polecenie zakończyło się powodzeniem, czy nie.
mattdm,
4
Dobre rozwiązanie - ale w przypadku nawarii niepotrzebnie śpi jeszcze jeden raz przed wyjściem.
ron rothman,
126
for i in 1 2 3 4 5; do command && break || sleep 15; done

Zamień „polecenie” na swoje polecenie. Zakłada się, że „kod statusu = FAIL” oznacza dowolny niezerowy kod powrotu.


Wariacje:

Korzystanie ze {..}składni. Działa w większości powłok, ale nie BusyBox sh:

for i in {1..5}; do command && break || sleep 15; done

Używanie seqi przekazywanie kodu wyjścia nieudanego polecenia:

for i in $(seq 1 5); do command && s=0 && break || s=$? && sleep 15; done; (exit $s)

To samo co powyżej, ale pomijanie sleep 15po ostatecznym niepowodzeniu. Ponieważ lepiej jest zdefiniować maksymalną liczbę pętli tylko raz, osiąga się to poprzez uśpienie na początku pętli, jeśli i > 1:

for i in $(seq 1 5); do [ $i -gt 1 ] && sleep 15; command && s=0 && break || s=$?; done; (exit $s)
Alexander
źródło
25
+1 - zwięzłe i jasne. Jedna sugestia: zastąpiłbym for i in 1 2 3 4 5ją, for i in {1..5}ponieważ jest łatwiejsza w utrzymaniu.
Paddy Landau,
5
Tylko uwaga, działa to, ponieważ wartość &&jest oceniana przed ze ||względu na pierwszeństwo operatora
gen_wood
6
Kolejna uwaga, zwróci kod 0, nawet jeśli się commandnie powiedzie.
Henrique Zambon
3
@HenriqueZambon Dodano wersję, która również to obsługuje.
Alexander
2
Czy to nie śpi po ostatecznej awarii? Wydaje się, że trzeba czekać 15 sekund. Myślę, że możesz to sprawdzić [[ i -eq 5]]przed snem, aby tego uniknąć.
Dave Lugg,
32
function fail {
  echo $1 >&2
  exit 1
}

function retry {
  local n=1
  local max=5
  local delay=15
  while true; do
    "$@" && break || {
      if [[ $n -lt $max ]]; then
        ((n++))
        echo "Command failed. Attempt $n/$max:"
        sleep $delay;
      else
        fail "The command has failed after $n attempts."
      fi
    }
  done
}

Przykład:

retry ping invalidserver

tworzy ten wynik:

ping: unknown host invalidserver
Command failed. Attempt 2/5:
ping: unknown host invalidserver
Command failed. Attempt 3/5:
ping: unknown host invalidserver
Command failed. Attempt 4/5:
ping: unknown host invalidserver
Command failed. Attempt 5/5:
ping: unknown host invalidserver
The command 'ping invalidserver' failed after 5 attempts

Aby zobaczyć prawdziwy przykład pracy ze złożonymi poleceniami, zobacz ten skrypt .

Fernando Correia
źródło
3
To świetne rozwiązanie. Podoba mi się, że kończy się z niezerowym statusem wyjścia po tym, jak coś zawiodło wiele razy.
Ben Liyanage,
11

Oto funkcja ponownej próby

function retry()
{
        local n=0
        local try=$1
        local cmd="${@: 2}"
        [[ $# -le 1 ]] && {
        echo "Usage $0 <retry_number> <Command>"; }

        until [[ $n -ge $try ]]
        do
                $cmd && break || {
                        echo "Command Fail.."
                        ((n++))
                        echo "retry $n ::"
                        sleep 1;
                        }

        done
}

retry $*

Wynik :

[test@Nagios ~]$ ./retry.sh 3 ping -c1 localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.207 ms

--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.207/0.207/0.207/0.000 ms

[test@Nagios ~]$ ./retry.sh 3 ping -c1 localhostlasjflasd
ping: unknown host localhostlasjflasd
Command Fail..
retry 1 ::
ping: unknown host localhostlasjflasd
Command Fail..
retry 2 ::
ping: unknown host localhostlasjflasd
Command Fail..
retry 3 ::
Rahul Patil
źródło
Skopiowałem twój kod do nowego pliku o nazwie retry.sh i dodałem wiersz #! / Bin / bash u góry. Podczas uruchamiania z podanymi komendami w objaśnieniu nic nie widzę, po prostu pojawia się ponownie monit.
java_enthu,
próbowałeśbash retry.sh 3 ping -c1 localhost
Rahul Patil
Tak, Rahul, próbowałem.
java_enthu,
Przepraszam, byłem bizy .., ponownie przetestowałem, działa, sprawdź wyjście paste.ubuntu.com/6002711
Rahul Patil
to jak dotąd najbardziej elegancka odpowiedź - jeśli robisz coś niebanalnego. Dzięki za poświęcenie czasu.
Jerry Andrews
10

GNU Parallel ma --retries:

parallel --retries 5 --delay 15s ::: ./do_thing.sh
Ole Tange
źródło
5

Oto mój ulubiony alias / skrypt w jednym wierszu

    alias retry='while [ $? -ne 0 ] ; do fc -s ; done'

Następnie możesz robić takie rzeczy jak:

     $ ps -ef | grep "Next Process"
     $ retry

i będzie uruchamiał poprzednie polecenie, aż znajdzie „Następny proces”

Jeff
źródło
1
W zsh użyj fc -e "#"zamiast fc -s.
Ricardo Stuven,
2

Używam tego skryptu, który wykonuje ponowną komendę, zaletą tego skryptu jest to, że jeśli nie powiedzie się wszystkie próby, zachowa kod wyjścia.

#!/usr/bin/env bash

if [ $# -ne 3 ]; then
    echo 'usage: retry <num retries> <wait retry secs> "<command>"'
    exit 1
fi

retries=$1
wait_retry=$2
command=$3

for i in `seq 1 $retries`; do
    echo "$command"
    $command
    ret_value=$?
    [ $ret_value -eq 0 ] && break
    echo "> failed with $ret_value, waiting to retry..."
    sleep $wait_retry
done

exit $ret_value

Prawdopodobnie może być łatwiej

padilo
źródło
Podoba mi się, jak elastyczna jest ta wersja oraz jak pełny i czytelny jest kod!
yo.ian.g
Aby dopasować nieudane echo, możesz nawet dodać udane echo za pomocą [$ ret_value -eq 0] lub przetestować później wartość $ ret_value
yo.ian.g
Ta wersja ma tę zaletę, że nie śpi po ostatnim niepowodzeniu polecenia.
Alexander,
1

Zobacz poniżej Przykład:

n=0
while :
do
        nc -vzw1 localhost 3859
        [[ $? = 0 ]] && break || ((n++))
        (( n >= 5 )) && break

done

Usiłuję podłączyć port 3389 na localhost, będzie ponawiał próbę aż do 5 razy nie powiedzie się, jeśli sukces zakończy się przerwaniem pętli.

$? istnieje status polecenia, jeśli zero oznacza, że ​​polecenie zostało pomyślnie uruchomione, jeśli inne niż zero oznacza polecenie fai

Wydaje się to trochę skomplikowane, być może ktoś zrobi to lepiej niż to.

Rahul Patil
źródło
Dzięki rahul .. czy będzie nadal próbować uruchomić skrypt?
Sandeep Singh,
Sprawdź teraz, zaktualizowałem
Rahul Patil,
$?to istnieje status polecenia jeśli zero oznacza pomyślnie uruchomić polecenie, jeśli inna niż zero oznacza komenda nie
Rahul Patil
czy wymagane jest podanie adresu hosta i portu? możemy to zrobić, podając tylko katalog lokalizacji skryptu.
Sandeep Singh,
zastąp dowolną komendą, która podaje kod statusu wyjścia $?
Rahul Patil,
1

Możesz użyć looppolecenia dostępnego tutaj :

$ loop './do_thing.sh' --every 15s --until-success --num 5 

Co zrobi twoje zadanie co 15 sekund, aż się powiedzie, maksymalnie pięć razy.

Rich Jones
źródło
0

Oto retryfunkcja rekurencyjna dla funkcjonalnych programistów purystów:

retry() {
  cmd=$1
  try=${2:-15}       # 15 by default
  sleep_time=${3:-3} # 3 seconds by default

  # Show help if a command to retry is not specified.
  [ -z "$1" ] && echo 'Usage: retry cmd [try=15 sleep_time=3]' && return 1

  # The unsuccessful recursion termination condition (if no retries left)
  [ $try -lt 1 ] && echo 'All retries failed.' && return 1

  # The successful recursion termination condition (if the function succeeded)
  $cmd && return 0

  echo "Execution of '$cmd' failed."

  # Inform that all is not lost if at least one more retry is available.
  # $attempts include current try, so tries left is $attempts-1.
  if [ $((try-1)) -gt 0 ]; then
    echo "There are still $((try-1)) retrie(s) left."
    echo "Waiting for $sleep_time seconds..." && sleep $sleep_time
  fi

  # Recurse
  retry $cmd $((try-1)) $sleep_time
}

Przekaż komendę (lub nazwę funkcji) i opcjonalnie liczbę ponownych prób oraz czas oczekiwania między kolejnymi próbami:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each
Michaił Vasin
źródło
Nie działa to w przypadku poleceń zawierających więcej niż jedno słowo: cmd = "echo bla bla" ... linia 10: [: blah: oczekiwano wyrażenia liczb całkowitych ... Ani to nie działa w przypadku potoków itp.
Mercury00