Como escrevo uma lógica de nova tentativa no script para continuar tentando executá-la até 5 vezes?

112

Quero escrever a lógica no shell script, que tentará executar novamente após 15 segundos, até 5 vezes, com base no "status code = FAIL", se falhar devido a algum problema.

Sandeep Singh
fonte

Respostas:

90

Este script usa um contador npara limitar as tentativas no comando para cinco. Se o comando for bem-sucedido, $?manterá zero e a execução será interrompida do loop.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done
suspeito
fonte
11
você deve adicionar breakse sucessos de comando, em seguida, ele vai quebrar o loop
Rahul Patil
Na verdade, a maneira correta de escrever que é if command; then break; fiou mais sucintamente apenascommand && break
tripleee
11
"comando" é apenas o nome do comando que você deseja verificar o status.
Suspeito # 11/07
3
Vale ressaltar que você pode testar se n é igual a cinco no final para saber se o comando foi bem-sucedido ou não.
mattdm
4
Solução agradável - mas, no caso de nfalhas, ele desnecessariamente dorme mais uma vez antes de sair.
ron rothman
126
for i in 1 2 3 4 5; do command && break || sleep 15; done

Substitua "comando" por seu comando. Isso pressupõe que "código de status = FAIL" significa qualquer código de retorno diferente de zero.


Variações:

Usando a {..}sintaxe. Funciona na maioria dos shells, mas não no BusyBox sh:

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

Usando seqe passando o código de saída do comando com falha:

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

O mesmo que acima, mas pulando sleep 15após a falha final. Como é melhor definir apenas o número máximo de loops uma vez, isso é conseguido adormecendo no início do loop se i > 1:

for i in $(seq 1 5); do [ $i -gt 1 ] && sleep 15; command && s=0 && break || s=$?; done; (exit $s)
Alexander
fonte
25
+1 - sucinto e claro. Uma sugestão: eu substituiria for i in 1 2 3 4 5por for i in {1..5}porque é mais fácil de manter.
Paddy Landau
5
Apenas uma nota, isso funciona porque o &&é avaliada antes da ||causa da precedência do operador
gene_wood
6
Outra observação, isso retornará o código 0, mesmo se commandfalhar.
Henrique Zambon
3
@HenriqueZambon Adicionada uma versão que também lida com isso.
Alexander
2
Isso não dorme após a falha final? Parece uma espera desnecessária de 15 anos. Eu acho que você pode colocar um cheque [[ i -eq 5]]como condição OR antes do sono para evitar isso.
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
}

Exemplo:

retry ping invalidserver

produz esta saída:

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 um exemplo de trabalho do mundo real com comandos complexos, consulte este script .

Fernando Correia
fonte
3
Esta é uma otima soluçao. Gosto que ele saia com um status de saída diferente de zero depois que algo falhou várias vezes também.
Ben Liyanage 23/02
11

Aqui está a função para tentar novamente

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

Resultado :

[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
fonte
Copiei colei seu código em um novo arquivo chamado retry.sh e adicionei uma linha #! / Bin / bash na parte superior. Ao executar com os comandos fornecidos na explicação, não vejo nada, apenas o prompt aparece novamente.
Java_enthu
você já tentoubash retry.sh 3 ping -c1 localhost
Rahul Patil
Sim Rahul eu tentei.
Java_enthu
Desculpe, eu estava Bizy .., eu testei novamente, ele está funcionando, verifique a saída paste.ubuntu.com/6002711
Rahul Patil
esta é a resposta mais elegante até agora - se você estiver fazendo algo não trivial. Obrigado por reservar um tempo.
Jerry Andrews
10

O GNU Parallel possui --retries:

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

Aqui está o meu alias / script favorito de uma linha

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

Então você pode fazer coisas como:

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

e continuará executando o comando anterior até encontrar o "Próximo processo"

Jeff
fonte
11
No zsh, use em fc -e "#"vez de fc -s.
Ricardo Stuven 17/08/19
2

Eu uso esse script que faz novas tentativas de um determinado comando, o benefício desse script é que, se todas as tentativas falharem, preservará o código de saída.

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

Provavelmente pode ficar mais simples

padilo
fonte
Eu gosto de quão flexível é esta versão e quão detalhado e legível é o código!
precisa saber é
Para corresponder ao eco com falha, você pode até adicionar um eco bem-sucedido com [$ ret_value -eq 0] ou testar o $ ret_value posteriormente
yo.ian.g
Esta versão tem a vantagem de não dormir após o comando falhar pela última vez.
Alexander
1

Veja abaixo o exemplo:

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

done

Estou tentando conectar a porta 3389 no host local, ele tentará novamente até 5 vezes, se for bem-sucedido, ele quebrará o loop.

$? existe o status de comando se zero significa que o comando é executado com sucesso, se diferente de zero significa comando fai

Parece um pouco complicado, pode ser que alguém faça isso melhor do que isso.

Rahul Patil
fonte
Obrigado rahul .. será repetido para executar o script ??
Sandeep Singh
Verifique agora, eu atualizei
Rahul Patil
$?é existir status do comando se zero significa comando executar com êxito, se diferente de zero significa comando falhar
Rahul Patil
é necessário fornecer o endereço do host e da porta. podemos fazer isso fornecendo apenas o diretório local do script.
Sandeep Singh
substituir por qualquer comando que dê o código de status de saída $?
Rahul Patil
1

Você pode usar o loopcomando, disponível aqui , da seguinte forma:

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

O que fará o seu trabalho a cada 15 segundos até que seja bem-sucedido, por no máximo cinco vezes.

Rich Jones
fonte
0

Aqui está uma retryfunção recursiva para puristas de programação 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
}

Passe um comando (ou um nome de função) e, opcionalmente, várias tentativas e uma duração de sono entre tentativas, da seguinte forma:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each
Mikhail Vasin
fonte
Isso não funciona para comandos com mais de uma palavra: cmd = "echo blá blá" ... linha 10: [: blá: expressão de número inteiro esperada ... Nem funciona para tubulações etc.
Mercury00