Как я могу получить этот скрипт для выхода из ошибки на основе результата цикла for?

13

У меня есть сценарий Bash, который использует set -o errexitтак, чтобы в случае ошибки весь сценарий выходил в точке сбоя.
Сценарий запускает curlкоманду, которая иногда не может получить нужный файл - однако, когда это происходит, сценарий не завершается с ошибкой.

Я добавил forцикл в

  1. сделать паузу на несколько секунд, затем повторить curlкоманду
  2. используйте falseв нижней части цикла for для определения ненулевого состояния выхода по умолчанию - если команда curl выполнена успешно - цикл прерывается, и состояние выхода последней команды должно быть равно нулю.
#! /bin/bash

set -o errexit

# ...

for (( i=1; i<5; i++ ))
do
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    if [ -f ~/.vim/autoload/pathogen.vim ]
    then
        echo "file has been retrieved by curl, so breaking now..."
        break;
    fi

    echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
    sleep 5
    # exit with non-zero status so main script will errexit
    false

done

# rest of script .....

Проблема в том, что при curlсбое команды цикл повторяет команду пять раз - если все попытки не удаются, цикл for завершается и основной сценарий возобновляется - вместо запуска errexit.
Как я могу получить весь скрипт для выхода, если этот curlоператор не выполняется?

the_velour_fog
источник

Ответы:

18

Заменить:

done

с:

done || exit 1

Это приведет к выходу кода, если forцикл завершается с ненулевым кодом выхода.

Как мелочи, 1в exit 1это не нужно. Обычная exitкоманда завершится со статусом выхода последней выполненной команды, которая будет false(code = 1), если загрузка не удалась. Если загрузка прошла успешно, код выхода цикла - это код выхода echoкоманды. echoобычно выходит с кодом = 0, успешно. В этом случае команда ||не запускается и exitкоманда не выполняется.

Наконец, обратите внимание, что set -o errexitможет быть полно сюрпризов. Для обсуждения его плюсов и минусов см . FAQ Грега # 105 .

Документация

От man bash:

для ((expr1; expr2; expr3)); сделать список; done
Во-первых, арифметическое выражение expr1 оценивается в соответствии с правилами, описанными ниже в разделе АРИФМЕТИЧЕСКАЯ ОЦЕНКА. Арифметическое выражение expr2 затем оценивается многократно, пока оно не станет равным нулю. Каждый раз, когда expr2 оценивается как ненулевое значение, выполняется список и вычисляется арифметическое выражение expr3. Если какое-либо выражение опущено, оно ведет себя так, как будто оно оценивается как 1. Возвращаемое значение - это состояние выхода последней команды в списке, которая выполняется, или значение false, если какое-либо из выражений недопустимо. [Акцент добавлен]

John1024
источник
Считаете ли вы, что было бы хорошей идеей поставить trueперед оператором break явное и обеспечить выходное значение цикла?
RobertL
1
Я думаю, что явное лучше, чем неявное . Вот почему я написал, exit 1когда просто exitбы сработало. Это вопрос стиля, и у других может быть свое мнение.
John1024
1
работает хорошо! спасибо :) лично я бы прочитал exitкак простой выход - который завершает скрипт самостоятельно. exit 1 будет читать мне как «сигнал» для какого-то другого процесса (то есть errexit) - что он должен завершить сценарий на основе «результата» exit 1. - так что я пошел с, exitно спасибо за объяснение
the_velour_fog
1
Если ваш скрипт завершается из-за ошибки, вы должны позвонить exit 1. Это никак не влияет errexit. Это просто говорит вызывающей программе, что что-то пошло не так. falseКоманда содержит одно утверждение: exit(1). 99,9% команд Unix возвращают 0 в случае успеха и ненулевое значение в случае ошибки. Твои тоже должны.
RobertL
2

Если вы errexitустановили, то falseоператор должен немедленно завершить работу скрипта. То же самое, если curlкоманда не удалась.

Ваш пример сценария, как написано, должен завершиться после первого curlсбоя команды при первом falseвызове, если установлен errexit.

Чтобы увидеть, как это работает (я использую сокращение -eдля установки errexit:

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

Таким образом, если curlкоманда выполняется более одного раза, этот сценарий не errexitустановлен.

RobertL
источник
1
set -eболее тонкий, чем это. Он не завершится после первой неудачной команды в цикле. Вы можете доказать это себе, запустив (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )и заметив, что код выполняется falseчетыре раза. Для получения дополнительной информации set -eсм . FAQ Грега # 105 .
John1024
@ John1024 Спасибо. Этот спускается вниз и вниз.
RobertL
@ John1024 Но я думаю, что доказательства все еще errexitне установлены. Пожалуйста, примените логику к сценарию в вопросе. Запустите это: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Да, тестирование возвращаемых значений с помощью if while || &&etc не вызывает ошибку errexit. Оригинальный скрипт не сделал ||цикл for.
RobertL
Я только что заметил, что не показал set -o errexitкоманду в своем примере кода, добавил ее сейчас - и для меня это не было ошибкой выхода, как ожидалось. Мне нужно было сохранить falseкак последнюю команду в цикле for, затем закрыть цикл с помощью done || exit [1]- тогда это сработало хорошо!
the_velour_fog
@RobertL Я понимаю вашу точку зрения.
Джон1024
1

set -o errexit может быть сложно в циклах и подоболочках, потому что вы должны пройти путь обратно из процесса.

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

i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
    [ $i -eq 1 ] || sleep 5
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    RET=$?
    i=$((i+1))
done
exit $RET
rexkogitans
источник
0

Если errexitустановлено и curlкоманда не выполняется, скрипт завершается сразу после неудачной команды curl. В руководстве по bash нет подсказки, в которой set -eигнорируется какой-либо сбойный статус возврата одного в составной команде. Это было бы только в том случае, если составная команда выполняется в контексте, где set -eигнорируется.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Попробуйте слегка адаптированный пример, опубликованный RobertL. Это останавливается на первой итерации for сразу после ложной команды:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )
G32RW
источник
0

Вы можете просто добавить параметр --fail к команде curl, это решит вашу проблему, скрипт завершится ошибкой и завершится с ошибкой, если команда curl не удастся, если это также очень полезно при использовании curl в конвейере jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
DevOps-Eng
источник