Як я можу змусити цей скрипт вийти з помилки на основі результату для циклу?


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заява не працює?

Відповіді:


18

Замінити:

done

з:

done || exit 1

Це призведе до виходу коду, якщо forцикл закривається з ненульовим кодом виходу.

З точки зору дрібниць, 1в exit 1не потрібен. Проста exitкоманда вийде зі статусом виходу останньої виконаної команди, яка була б false(код = 1), якщо завантаження не вдалося . Якщо завантаження проходить успішно, вихідним кодом циклу є код виходу echoкоманди. echoзазвичай виходить з кодом = 0, сигнал успішно. У такому випадку команда ||не спрацьовує і exitкоманда не виконується.

Нарешті, зауважте, що set -o errexitможе бути повно сюрпризів. Для обговорення його плюсів і мінусів дивіться старі запитання Грега № 105 .

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

Від man bash:

for ((expr1; expr2; expr3)); робити список; зроблено
По-перше, арифметичний вираз expr1 оцінюється за правилами, описаними нижче в АРИТМЕТИЧНІЙ ОЦІНКІ. Арифметичний вираз expr2 потім оцінюється багаторазово, поки він не оцінюється до нуля. Кожен раз, коли expr2 оцінюється на нульове значення, виконується список і оцінюється арифметичне вираження expr3. Якщо будь-який вираз опущено, він поводиться так, ніби він оцінюється на 1. Повернене значення - це статус виходу останньої команди, що виконується у списку, або помилкове, якщо будь-який із виразів недійсний. [Наголос додано]


Як ви вважаєте, було б гарною ідеєю поставити 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встановлений.


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 не призводить до помилки. Оригінальний сценарій не ||був циклом for.
RobertL

Щойно я помітив, що я не показав set -o errexitкоманду в своєму прикладі коду, додав її зараз - і для мене це не було помилкою, як очікувалося. Мені потрібно було зберегти falseяк останню команду в циклі for, потім закрити цикл з done || exit [1]- тоді воно працювало чудово!
the_velour_fog

@RobertL Я бачу вашу думку.
John1024

1

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

Розрив петлі (навіть при нормальній роботі) вважається поганою практикою. Ви можете зателефонувати мені в стару школу, щоб віддати перевагу циклу while, а не циклу для двох умов, але я вважаю, що краще прочитати:

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

0

Якщо errexitвстановлено і curlкоманда не вдається, сценарій закінчується відразу після невдалої команди curl. У посібнику з bash немає жодної підказки, яка set -eігнорує будь-який невдалий статус повернення синглу в складеній команді. Це було б лише у випадку, якщо складна команда виконується в контексті, де set -eвона ігнорується.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Спробуйте трохи адаптований приклад, розміщений RobertL. Це зупиняється на першій ітерації відразу після помилкової команди:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )

0

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

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.