Come faccio a scrivere una logica di nuovo tentativo nello script per continuare a riprovare per eseguirla fino a 5 volte?


112

Voglio scrivere la logica nello script della shell che riprovi a eseguirla nuovamente dopo 15 secondi fino a 5 volte in base a "codice stato = FAIL" se non riesce a causa di un problema.

Risposte:


90

Questo script utilizza un contatore nper limitare a cinque i tentativi di comando. Se il comando ha esito positivo, $?manterrà zero e l'esecuzione verrà interrotta dal ciclo.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done

1
dovresti aggiungere breakse il comando ha esito positivo, quindi si interromperà il ciclo
Rahul Patil,

In realtà il modo corretto di scrivere che è if command; then break; fio più succintamente solocommand && break
tripleee

1
"comando" è solo il nome del comando di cui si desidera verificare lo stato.
suspectus,

3
Vale la pena notare che è possibile verificare se n è uguale a cinque alla fine per sapere se il comando ha avuto esito positivo o meno.
Mattdm,

4
Bella soluzione - ma in caso di nguasti, dormire inutilmente un'altra volta prima di uscire.
Ron Rothman,

126
for i in 1 2 3 4 5; do command && break || sleep 15; done

Sostituisci "comando" con il tuo comando. Ciò presuppone che "code status = FAIL" significhi qualsiasi codice di ritorno diverso da zero.


variazioni:

Utilizzando la {..}sintassi. Funziona nella maggior parte delle shell, ma non in BusyBox sh:

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

Utilizzo seqe passaggio del codice di uscita del comando non riuscito:

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

Come sopra, ma saltando sleep 15dopo il fallimento finale. Poiché è meglio definire il numero massimo di loop una sola volta, ciò si ottiene dormendo all'inizio del loop se i > 1:

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

25
+1 - succinto e chiaro. Un suggerimento: io sostituirei for i in 1 2 3 4 5con for i in {1..5}perché è più facile da mantenere.
Paddy Landau,

5
Solo una nota, questo funziona perché &&viene valutato prima del ||per precedenza dell'operatore
gene_wood

6
Un'altra nota, questo restituirà il codice 0 anche se commandfallisce.
Henrique Zambon,

3
@HenriqueZambon Aggiunta una versione che gestisce anche quella.
Alexander,

2
Questo non dorme dopo l'ultimo fallimento? Sembra un'attesa inutile di 15 secondi. Penso che puoi [[ i -eq 5]]evitare un controllo come condizione OR prima del sonno.
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
}

Esempio:

retry ping invalidserver

produce questo output:

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

Per un esempio reale e funzionante con comandi complessi, vedere questo script .


3
Questa è un'ottima soluzione Mi piace che esca con uno stato di uscita diverso da zero dopo che qualcosa è fallito anche più volte.
Ben Liyanage,

11

Ecco la funzione per riprovare

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 $*

Produzione :

[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 ::

Copio incollato il codice in un nuovo file chiamato retry.sh e ho aggiunto una riga #! / Bin / bash in alto. Durante l'esecuzione con i comandi forniti nella spiegazione, non vedo nulla, solo il prompt viene di nuovo.
java_enthu,

hai provatobash retry.sh 3 ping -c1 localhost
Rahul Patil,

Sì, Rahul ci ho provato.
java_enthu,

Scusate, sono stato bizzarro .., ho testato di nuovo, funziona, controlla l'output paste.ubuntu.com/6002711
Rahul Patil

questa è la risposta più elegante qui fino ad ora - se stai facendo qualcosa di non banale. Grazie per aver dedicato del tempo.
Jerry Andrews

10

GNU Parallel ha --retries:

parallel --retries 5 --delay 15s ::: ./do_thing.sh

5

Ecco il mio alias / script preferito di una riga

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

Quindi puoi fare cose come:

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

e continuerà a eseguire il comando precedente fino a quando non trova "Processo successivo"


1
In zsh, utilizzare fc -e "#"invece di fc -s.
Ricardo Stuven,

2

Uso questo script che effettua i tentativi di un determinato comando, il vantaggio di questo script è che se fallisce tutti i tentativi, manterrà il codice di uscita.

#!/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

Probabilmente può essere più semplice


Mi piace quanto sia flessibile questa versione e quanto sia dettagliato e leggibile il codice!
yo.ian.g

Per abbinare l'eco fallito, potresti anche aggiungere un eco riuscito con [$ ret_value -eq 0] o testare successivamente $ ret_value
yo.ian.g

Questa versione ha il vantaggio di non dormire dopo che il comando ha avuto esito negativo per l'ultima volta.
Alexander,

1

Vedi sotto Esempio:

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

done

Sto provando a connettere la porta 3389 su localhost, riproverà fino a quando falliranno 5 volte, se il successo interromperà il ciclo.

$? esiste lo stato del comando se zero indica che il comando è stato eseguito correttamente, se diverso da zero indica il comando fai

Sembra un po 'complicato, forse qualcuno lo fa meglio di così.


Grazie rahul .. sarà necessario riprovare a eseguire lo script ??
Sandeep Singh,

Per favore, controlla ora, ho aggiornato
Rahul Patil l'

$?esiste lo stato del comando se zero significa che il comando è stato eseguito correttamente, se diverso da zero significa che il comando ha esito negativo
Rahul Patil

è necessario fornire l'host e l'indirizzo della porta. possiamo farlo fornendo solo la directory della posizione dello script.
Sandeep Singh,

sostituire con qualsiasi comando che dia il codice di stato di uscita $?
Rahul Patil,

1

Puoi usare il loopcomando, disponibile qui , in questo modo:

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

Che farà il tuo dovere ogni 15 secondi fino a quando non avrà successo, per un massimo di cinque volte.


0

Ecco una retryfunzione ricorsiva per i puristi della programmazione funzionale:

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
}

Passalo un comando (o un nome di funzione) e facoltativamente un numero di tentativi e una durata del sonno tra i tentativi, in questo modo:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each

Questo non funziona per comandi lunghi più di una parola: cmd = "echo blah blah" ... riga 10: [: blah: espressione intera prevista ... Né funziona per pipe, ecc.
Mercury00
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.