Как мне написать логику повторов в скрипте, чтобы повторять попытки запуска до 5 раз?

112

Я хочу написать логику в сценарии оболочки, которая будет повторять его запуск через 15 секунд до 5 раз, основываясь на «status code = FAIL», если произойдет сбой из-за какой-либо проблемы.

Сандип Сингх
источник

Ответы:

91

Этот скрипт использует счетчик, nчтобы ограничить количество попыток выполнения команды до пяти. Если команда выполнена успешно, $?будет удерживать ноль и выполнение будет прерываться из цикла.

n=0
until [ $n -ge 5 ]
do
   command && break  # substitute your command here
   n=$[$n+1]
   sleep 15
done
suspectus
источник
1
Вы должны добавить, что breakесли команда добьется успеха, то она разорвет петлю
Рахул Патил
На самом деле правильный способ написать это if command; then break; fiили более кратко простоcommand && break
tripleee
1
«команда» - это просто имя команды, статус которой вы хотите проверить.
подозреваемый
3
Стоит отметить, что вы можете проверить, равно ли n в конце пяти, чтобы узнать, была ли команда выполнена успешно или нет.
mattdm
4
Хорошее решение - но в случае nсбоев он без необходимости спит еще один раз перед выходом.
Рон Ротман
127
for i in 1 2 3 4 5; do command && break || sleep 15; done

Замените «команда» вашей командой. Это предполагает, что «код состояния = ОТКАЗ» означает любой ненулевой код возврата.


Варианты:

Используя {..}синтаксис. Работает в большинстве оболочек, но не в BusyBox sh:

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

Используя seqи передавая код завершения неудачной команды:

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

То же, что и выше, но пропускаем sleep 15после окончательного сбоя. Поскольку лучше определить максимальное количество циклов только один раз, это достигается путем перехода в спящий режим в начале цикла, если i > 1:

for i in $(seq 1 5); do [ $i -gt 1 ] && sleep 15; command && s=0 && break || s=$?; done; (exit $s)
Александр
источник
25
+1 - лаконично и понятно. Одно из предложений: я бы заменить for i in 1 2 3 4 5с , for i in {1..5}потому что это легче поддерживать.
Пэдди Ландау
5
Просто обратите внимание, это работает, потому что &&вычисляется раньше ||из-за приоритета оператора
gene_wood
6
Еще одно примечание, это вернет код 0, даже если произойдет commandсбой.
Энрике
3
@HenriqueZambon Добавлена ​​версия, которая также обрабатывает это.
Александр
2
Разве это не спит после последней неудачи? Похоже, ненужные 15 секунд ожидания. Я думаю, что вы можете поставить чек на [[ i -eq 5]]состояние ИЛИ перед сном, чтобы избежать этого.
Дейв Лугг
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
}

Пример:

retry ping invalidserver

производит этот вывод:

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

Для реального рабочего примера со сложными командами см. Этот скрипт .

Фернандо Коррейя
источник
3
Это отличное решение. Мне нравится, что он выходит с ненулевым статусом выхода после того, как что-то не получилось несколько раз.
Бен
11

Вот функция для повтора

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

Выход :

[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 ::
Рахул Патил
источник
Я скопировал и вставил ваш код в новый файл с именем retry.sh и добавил строку #! / Bin / bash вверху. Во время работы с вашими заданными командами в объяснении я не вижу ничего, просто приглашение приходит снова.
java_enthu
Вы пробовалиbash retry.sh 3 ping -c1 localhost
Рахул Патил
Да, Рахул, я попробовал.
java_enthu
Извините, я был бизом .., я снова проверил, все работает, проверьте вывод paste.ubuntu.com/6002711
Рахул Патил
пока что это самый элегантный ответ здесь - если вы делаете что-то нетривиальное. Спасибо, что нашли время.
Джерри Эндрюс
10

GNU Parallel имеет --retries:

parallel --retries 5 --delay 15s ::: ./do_thing.sh
Оле Танге
источник
5

Вот мой любимый однострочный псевдоним / скрипт

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

Тогда вы можете делать такие вещи, как:

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

и он продолжит выполнение предыдущей команды, пока не найдет «Следующий процесс»

Джефф
источник
1
В zsh используйте fc -e "#"вместо fc -s.
Рикардо Стювен
2

Я использую этот сценарий, который выполняет повторные попытки данной команды, преимущество этого сценария в том, что в случае неудачи при всех повторных попытках он сохранит код завершения.

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

Возможно, это может стать проще

padilo
источник
Мне нравится, насколько гибка эта версия и насколько подробен и удобен для чтения код!
yo.ian.g
Чтобы сопоставить ошибочное эхо, вы можете даже добавить успешное эхо с помощью [$ ret_value -eq 0] или протестировать $ ret_value впоследствии
yo.ian.g
Преимущество этой версии состоит в том, что она не спит после того, как команда не работает в последний раз.
Александр
1

Смотрите ниже Пример:

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

done

Я пытаюсь подключить порт 3389 на localhost, он будет повторяться до тех пор, пока не произойдет сбой 5 раз, в случае успеха он прервет цикл.

$? это существующий статус команды, если ноль означает, что команда успешно запущена, если кроме нуля означает команду fai

Кажется, немного сложнее, может быть, кто-то делает это лучше, чем это.

Рахул Патил
источник
Спасибо, Рахул. Будет ли повторная попытка запустить скрипт?
Сандип Сингх
Пожалуйста, проверьте сейчас, я обновил
Рахул Патил
$?это существующий статус команды, если она равна нулю, означает, что команда успешно запущена, если кроме нуля означает, что команда не выполнена
Рахул Патил,
Требуется ли указать адрес хоста и порта. Можем ли мы сделать это, указав только местоположение каталога.
Сандип Сингх
заменить на любую команду, которая дает код состояния выхода $?
Рахул Патил
1

Вы можете использовать loopкоманду, доступную здесь , вот так:

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

Который будет делать ваше дело каждые 15 секунд, пока не добьется успеха, максимум пять раз.

Рич Джонс
источник
0

Вот рекурсивная retryфункция для пуристов функционального программирования:

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
}

Передайте ему команду (или имя функции) и, необязательно, количество повторных попыток и продолжительность ожидания между повторными попытками, например так:

retry some_command_or_fn 5 15 # 5 tries, sleep 15 seconds between each
Михаил Васин
источник
Это не работает для команд, длина которых превышает одно слово: cmd = "echo blah blah" ... строка 10: [: blah: ожидается целочисленное выражение ... Также это не работает для каналов и т. Д.
Mercury00