Comment puis-je écrire une logique de nouvelle tentative dans le script pour continuer à essayer de l'exécuter jusqu'à 5 fois?

112

Je veux écrire la logique dans un script shell qui le réessayera de s'exécuter de 15 à 5 secondes en fonction du "code d'état = FAIL" s'il échoue à cause d'un problème.

Sandeep Singh
la source

Réponses:

91

Ce script utilise un compteur npour limiter le nombre de tentatives à la commande à cinq. Si la commande aboutit, sa valeur $?sera égale à zéro et l'exécution sera interrompue.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done
suspect
la source
1
vous devez ajouter que breaksi la commande réussit, la boucle sera rompue
Rahul Patil
En fait, la bonne façon d'écrire est if command; then break; fiou plus succinctement justecommand && break
triple-
1
"commande" est simplement le nom de la commande dont vous voulez vérifier le statut.
suspectus
3
Il est à noter que vous pouvez tester si n est égal à cinq à la fin pour savoir si la commande a réussi ou non.
mattdm
4
Belle solution - mais en cas d' néchec, il dort inutilement une fois de plus avant de sortir.
Ron Rothman
127
for i in 1 2 3 4 5; do command && break || sleep 15; done

Remplacez "commande" par votre commande. Cela suppose que "code d'état = FAIL" désigne tout code de retour différent de zéro.


Variations

Utiliser la {..}syntaxe. Fonctionne dans la plupart des shells, mais pas dans BusyBox sh:

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

Utilisation seqet transmission du code de sortie de la commande ayant échoué:

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

Comme ci-dessus, mais en sautant sleep 15après l'échec final. Dans la mesure où il est préférable de définir le nombre maximal de boucles une seule fois, vous pouvez vous mettre en veille au début de la boucle si i > 1:

for i in $(seq 1 5); do [ $i -gt 1 ] && sleep 15; command && s=0 && break || s=$?; done; (exit $s)
Alexandre
la source
25
+1 - succinct et clair. Une suggestion: je remplacerais for i in 1 2 3 4 5par for i in {1..5}parce que c'est plus facile à maintenir.
Paddy Landau
5
Juste une note, cela fonctionne car le &&est évalué avant le en ||raison de la priorité
gene_wood
6
Une autre note, cela retournera le code 0 même si commandéchoue.
Henrique Zambon
3
@HenriqueZambon Ajout d'une version qui gère également cela.
Alexander
2
Est-ce que cela ne dort pas après l'échec final? On dirait une attente inutile de 15 secondes. Je pense que vous pouvez mettre un chèque [[ i -eq 5]]comme condition d’opération avant le sommeil pour éviter cela.
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
}

Exemple:

retry ping invalidserver

produit cette sortie:

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

Pour un exemple concret, avec des commandes complexes, voir ce script .

Fernando Correia
la source
3
C'est une excellente solution. J'aime le fait qu'il se termine avec un statut de sortie différent de zéro après un échec répété à plusieurs reprises.
Ben Liyanage
11

Voici la fonction pour réessayer

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

Sortie:

[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
la source
Je copie votre code collé dans un nouveau fichier appelé retry.sh et ajoute une ligne #! / Bin / bash en haut. Lors de l'exécution avec vos commandes données dans l'explication, je ne vois rien, une invite vient à nouveau.
java_enthu
avez-vous essayébash retry.sh 3 ping -c1 localhost
Rahul Patil
Oui Rahul j'ai essayé.
java_enthu
Désolé, j'étais bizarre .., j'ai testé à nouveau, ça fonctionne, vérifiez la sortie paste.ubuntu.com/6002711
Rahul Patil
c'est la réponse la plus élégante ici jusqu'à présent - si vous faites quelque chose de non trivial. Merci de prendre le temps.
Jerry Andrews
10

GNU Parallel a --retries:

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

Voici mon alias / script préféré d'une ligne

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

Ensuite, vous pouvez faire des choses comme:

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

et il continuera à exécuter la commande précédente jusqu'à ce qu'il trouve "Prochain processus"

Jeff
la source
1
Dans zsh, utilisez à la fc -e "#"place de fc -s.
Ricardo Stuven
2

J'utilise ce script qui effectue les tentatives d'une commande donnée. L'avantage de ce script est que si toutes les tentatives échouent, le code de sortie est préservé.

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

Probablement cela peut devenir plus simple

padilo
la source
J'aime la souplesse de cette version et la lisibilité et la lisibilité du code!
yo.ian.g
Pour correspondre à l'écho échoué, vous pouvez même ajouter un écho réussi avec le paramètre [$ ret_value -eq 0] ou tester le paramètre $ ret_value par la suite
yo.ian.g
Cette version présente l’avantage de ne pas dormir après l’échec de la commande pour la dernière fois.
Alexander
1

Voir ci-dessous Exemple:

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

done

J'essaye de connecter le port 3389 sur localhost, il essaiera jusqu'à 5 fois d'échouer, en cas de succès, il cassera la boucle.

$? il existe un statut de commande si zéro signifie une commande exécutée avec succès, si autre que zéro signifie une commande fai

Cela semble un peu compliqué, peut-être que quelqu'un le fera mieux que cela.

Rahul Patil
la source
Merci, rahul .. est-ce que ce sera continuer d'essayer de lancer le script?
Sandeep Singh
S'il vous plaît vérifier maintenant, j'ai mis à jour
Rahul Patil
$?il existe un statut de commande si zéro signifie une commande exécutée avec succès, si autre que zéro signifie une commande échouée
Rahul Patil
est-il nécessaire de donner l'hôte et l'adresse du port. pouvons-nous le faire en donnant uniquement le répertoire emplacement du script.
Sandeep Singh
remplacer par n'importe quelle commande donnant le code de statut de sortie $?
Rahul Patil
1

Vous pouvez utiliser la loopcommande, disponible ici , comme suit:

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

Ce qui fera votre chose toutes les 15 secondes jusqu'à ce qu'il réussisse, pour un maximum de cinq fois.

Rich Jones
la source
0

Voici une retryfonction récursive pour les puristes de la programmation fonctionnelle:

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
}

Transmettez-lui une commande (ou un nom de fonction) et éventuellement un nombre de tentatives et une durée de veille entre les tentatives, comme suit:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each
Mikhail Vasin
la source
Cela ne fonctionne pas pour les commandes de plus d'un mot: cmd = "echo blah blah" ... ligne 10: [: blah: expression entière attendue ... Cela ne fonctionne pas non plus pour les pipes, etc.
Mercury00