¿Cómo escribo una lógica de reintento en el script para volver a intentar ejecutarlo hasta 5 veces?

112

Quiero escribir lógica en el script de shell que volverá a intentar ejecutarlo después de 15 segundos hasta 5 veces según "código de estado = FALLO" si falla debido a algún problema.

Sandeep Singh
fuente

Respuestas:

91

Este script usa un contador npara limitar los intentos del comando a cinco. Si el comando tiene éxito, $?mantendrá cero y la ejecución se interrumpirá del bucle.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done
sospechoso
fuente
1
debe agregar breaksi el comando tiene éxito, entonces se romperá el ciclo
Rahul Patil
En realidad, la forma correcta de escribir que es if command; then break; fio más sucintamente solocommand && break
tripleee
1
"comando" es solo el nombre del comando del que desea verificar el estado.
sospechoso
3
Vale la pena señalar que puede probar si n es igual a cinco al final para saber si el comando tuvo éxito o no.
mattdm
44
Buena solución, pero en caso de nfallas, duerme innecesariamente una vez más antes de salir.
Ron Rothman
127
for i in 1 2 3 4 5; do command && break || sleep 15; done

Reemplace "comando" con su comando. Esto supone que "código de estado = FALLO" significa cualquier código de retorno distinto de cero.


Variaciones:

Usando la {..}sintaxis. Funciona en la mayoría de los shells, pero no en BusyBox sh:

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

Usando seqy pasando el código de salida del comando fallido:

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

Igual que el anterior, pero omitiendo sleep 15después del fallo final. Dado que es mejor definir solo el número máximo de bucles una vez, esto se logra durmiendo al comienzo del bucle si i > 1:

for i in $(seq 1 5); do [ $i -gt 1 ] && sleep 15; command && s=0 && break || s=$?; done; (exit $s)
Alejandro
fuente
25
+1 - sucinto y claro. Una sugerencia: reemplazaría for i in 1 2 3 4 5con for i in {1..5}porque es más fácil de mantener.
Paddy Landau
55
Solo una nota, esto funciona porque &&se evalúa antes que ||por la precedencia
gene_wood
66
Otra nota, esto devolverá el código 0 incluso si commandfalla.
Henrique Zambon
3
@HenriqueZambon Agregó una versión que también maneja eso.
Alexander
2
¿No duerme esto después del fracaso final? Parece una espera innecesaria de 15 años. Creo que puede poner un cheque [[ i -eq 5]]como una condición OR antes del sueño para evitar esto.
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
}

Ejemplo:

retry ping invalidserver

produce esta salida:

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

Para ver un ejemplo práctico del mundo real con comandos complejos, consulte este script .

Fernando Correia
fuente
3
Esta es una gran solución. Me gusta que salga con un estado de salida distinto de cero después de que algo haya fallado varias veces también.
Ben Liyanage
11

Aquí está la función para reintentar

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

Salida:

[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
fuente
Copié y pegué su código en un nuevo archivo llamado retry.sh y agregué una línea #! / Bin / bash en la parte superior. Mientras se ejecuta con sus comandos dados en la explicación, no veo nada, solo aparece el mensaje nuevamente.
java_enthu
¿Has probadobash retry.sh 3 ping -c1 localhost
Rahul Patil
Sí, Rahul, lo intenté.
java_enthu
Lo siento, estaba bizy ..., lo he probado nuevamente, está funcionando, verifique la salida paste.ubuntu.com/6002711
Rahul Patil
Esta es la respuesta más elegante aquí hasta ahora, si estás haciendo algo no trivial. Gracias por tomarte el tiempo.
Jerry Andrews
10

GNU Parallel tiene --retries:

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

Aquí está mi alias / script favorito de una línea

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

Entonces puedes hacer cosas como:

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

y seguirá ejecutando el comando anterior hasta que encuentre "Siguiente proceso"

Jeff
fuente
1
En zsh, use en fc -e "#"lugar de fc -s.
Ricardo Stuven
2

Utilizo este script que realiza los reintentos de un comando dado, el beneficio de este script es que si falla todos los reintentos conservará el código de salida.

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

Probablemente puede ser más simple

padilo
fuente
¡Me gusta lo flexible que es esta versión y cuán detallado y legible es el código!
yo.ian.g
Para hacer coincidir el eco fallido, incluso podría agregar un eco exitoso con [$ ret_value -eq 0] o probar el $ ret_value después
yo.ian.g
Esta versión tiene la ventaja de no dormir después de que el comando falla por última vez.
Alexander
1

Ver abajo Ejemplo:

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

done

Estoy tratando de conectar el puerto 3389 en localhost, volverá a intentarlo hasta que falle 5 veces, si tiene éxito, romperá el ciclo.

$? es el estado del comando si cero significa que el comando se ejecutó con éxito, si otro que cero significa comando fai

Parece un poco complicado, puede ser que alguien lo haga mejor que esto.

Rahul Patil
fuente
Gracias rahul ... ¿volverá a intentar ejecutar el script?
Sandeep Singh
Por favor, compruebe ahora, he actualizado
Rahul Patil
$?es el estado del comando si cero significa que el comando se ejecutó correctamente, si otro que cero significa que el comando falla
Rahul Patil
¿Es necesario dar el host y la dirección del puerto? podemos hacerlo dando solo la ubicación del script dir.
Sandeep Singh
reemplace con cualquier comando que proporcione el código de estado de salida $?
Rahul Patil
1

Puede usar el loopcomando, disponible aquí , así:

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

Que hará lo suyo cada 15 segundos hasta que tenga éxito, por un máximo de cinco veces.

Rich Jones
fuente
0

Aquí hay una retryfunción recursiva para puristas de programación funcional:

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
}

Páselo un comando (o un nombre de función) y, opcionalmente, una serie de reintentos y una duración de suspensión entre reintentos, de esta manera:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each
Mikhail Vasin
fuente
Esto no funciona para comandos de más de una palabra: cmd = "echo blah blah" ... línea 10: [: blah: se espera una expresión entera ... Tampoco funciona para tuberías, etc.
Mercury00