
У меня есть скрипт bash, который использует set -o errexit
так, что при ошибке весь скрипт завершается в точке сбоя.
Скрипт запускает curl
команду, которая иногда не может получить нужный файл - однако, когда это происходит, скрипт не завершается с ошибкой.
Я добавил for
петлю к
- подождите несколько секунд, затем повторите
curl
команду - используйте
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
оператор не выполняется?
решение1
Заменять:
done
с:
done || exit 1
Это приведет к выходу кода, если for
цикл завершится с ненулевым кодом выхода.
В качестве мелочи, 1
in exit 1
не нужен. Обычная exit
команда завершит работу со статусом выхода последней выполненной команды, который будет false
(code=1), если загрузка не удалась. Если загрузка прошла успешно, код выхода цикла будет кодом выхода команды echo
. echo
обычно завершается с кодом=0, что является сигналом успеха. В этом случае ||
не срабатывает и exit
команда не выполняется.
Наконец, обратите внимание, что set -o errexit
может быть полно сюрпризов. Для обсуждения его плюсов и минусов см.Часто задаваемые вопросы Грега #105.
Документация
От man bash
:
для(( выражение1 ; выражение2 ; выражение3 )) ;делатьсписок ; сделанный
Сначала арифметическое выражение expr1 вычисляется в соответствии с правилами, описанными ниже в разделе АРИФМЕТИЧЕСКАЯ ОЦЕНКА. Затем арифметическое выражение expr2 вычисляется повторно, пока не станет равным нулю. Каждый раз, когда expr2 вычисляется как ненулевое значение, выполняется list и вычисляется арифметическое выражение expr3. Если какое-либо выражение опущено, оно ведет себя так, как будто оно равно 1. Возвращаемое значение — это статус завершения последней выполненной команды в списке или false, если какое-либо из выражений недопустимо.[Выделено мной]
решение2
Если вы предпочитаете остановить цикл при первой ошибке, а также избежать set -e
, вы можете сделать что-то вроде этого:
for i in `seq 1 10`; do
run_command || exit 1;
done
решение3
Если вы установили errexit
, то false
оператор должен вызвать немедленный выход из скрипта. То же самое, если curl
команда не удалась.
Ваш пример скрипта, как он написан, должен завершить работу после первой curl
ошибки команды при первом вызове, false
если установлен errexit.
Чтобы увидеть, как это работает (я использую сокращение -e
для установки errexit
):
$ ( set -e; false; echo still here )
$
$ ( set +e; false; echo still here )
still here
$
Таким образом, если curl
команда выполняется более одного раза, этот скрипт не установлен errexit
.
решение4
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