Прерывание сценария оболочки, если какая-либо команда возвращает ненулевое значение?

277

У меня есть Bash shell script, который вызывает несколько команд. Я хотел бы, чтобы оболочка script автоматически выходила с возвратным значением 1, если какая-либо из команд возвращает ненулевое значение.

Возможно ли это без явной проверки результата каждой команды?

например.

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
  • 7
    В дополнение к set -e , также выполните set -u (или set -eu ). -u положил конец идиотскому, скрывающему ошибки поведению: вы можете получить доступ к любой несуществующей переменной и получить пустое значение без диагностики.
Теги:

9 ответов

548
Лучший ответ

Добавьте это в начало script:

set -e

Это приведет к немедленному завершению работы оболочки, если простая команда завершится с ненулевым значением выхода. Простой командой является любая команда, не являющаяся частью if, while или до теста или частью && или || список.

Подробнее см. bash (1) справочную страницу по внутренней команде "set".

Я лично запускаю почти все сценарии оболочки с помощью "set -e". Это действительно раздражает, чтобы иметь script упорно продолжать, когда что-то не в середине и разбивает предположения для остальной части script.

  • 35
    Это бы сработало, но мне нравится использовать "#! / Usr / bin / env bash", потому что я часто запускаю bash где-то, кроме / bin. И "#! / Usr / bin / env bash -e" не работает. Кроме того, приятно иметь место для изменения, чтобы читать «set -xe», когда я хочу включить трассировку для отладки.
  • 2
    Имейте в виду, что этого может быть недостаточно из-за трубопроводов. Смотрите мой добавленный ответ.
Показать ещё 8 комментариев
137

Чтобы добавить к принятому ответу:

Помните, что иногда set -e недостаточно, особенно если у вас есть трубы.

Например, предположим, что у вас есть этот script

#!/bin/bash
set -e 
./configure  > configure.log
make

... работает как ожидалось: ошибка в configure прерывает выполнение.

Завтра вы делаете кажущееся тривиальное изменение:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... и теперь это не сработает. Это объясняется здесь, и обходной путь (Bash) предоставляется:

#!/bin/bash
set -e 
set -o pipefail

./configure  | tee configure.log
make
  • 0
    Спасибо, что объяснили важность наличия pipefail чтобы согласиться с set -o !
63

Операторы if в вашем примере не нужны. Просто сделайте это так:

dosomething1 || exit 1

Если вы берете совет Ville Laurikari и используете set -e, то для некоторых команд вам может понадобиться использовать это:

dosomething || true

|| true приведет к тому, что конвейер команды получит возвращаемое значение true, даже если команда завершилась неудачей, поэтому параметр -e не будет убивать script.

  • 1
    Мне это нравится. Тем более, что главный ответ - bash-centric (мне вообще не ясно, относится ли / в какой степени к сценариям zsh). И я мог бы это посмотреть, но ваша просто понятнее, потому что логика.
23

Если у вас есть очистка, которую вы должны выполнить при выходе, вы также можете использовать "ловушку" с ERR псевдосигнала. Это работает так же, как захват INT или любого другого сигнала; bash выдает ERR, если какая-либо команда выходит с ненулевым значением:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Или, особенно если вы используете "set -e", вы можете заблокировать EXIT; тогда ваша ловушка будет выполнена, когда script выйдет по какой-либо причине, включая нормальный конец, прерывания, выход, вызванный параметром -e и т.д.

11

Запустите его с помощью -e или set -e вверху.

Также смотрите set -u.

  • 28
    Чтобы потенциально спасти других, необходимо прочитать help set : -u рассматривает ссылки на неустановленные переменные как ошибки.
  • 1
    так что это либо set -u либо set -e , а не оба? @lumpynose
Показать ещё 1 комментарий
8

Переменная $? редко требуется. Псевдо-идиома command; if [ $? -eq 0 ]; then X; fi всегда должна быть записана как if command; then X; fi.

Случаи, когда требуется $?, необходимо проверить на несколько значений:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

или когда $? необходимо повторно использовать или иным образом манипулировать:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
  • 1
    Почему «всегда должно быть написано как»? Я имею в виду, почему "так" должно быть? Когда команда длинная (например, вызывая GCC с дюжиной опций), тогда гораздо удобнее выполнить команду перед проверкой состояния возврата.
  • 0
    Если команда слишком длинная, вы можете ее разбить, назвав ее (определите функцию оболочки).
Показать ещё 1 комментарий
3

Выражение вроде

dosomething1 && dosomething2 && dosomething3

прекратит обработку, когда одна из команд вернется с ненулевым значением. Например, следующая команда никогда не будет печатать "done":

cat nosuchfile && echo "done"
echo $?
1
1
#!/bin/bash -e

должно быть достаточно.

-3

просто бросает в другой для справки, так как был добавлен дополнительный вопрос для ввода Mark Edgars, и вот еще один пример и касается темы в целом:

[[ `cmd` ]] && echo success_else_silence

что совпадает с cmd || exit errcode, как показал кто-то.

например. Я хочу, чтобы раздел был отключен, если он установлен:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
  • 4
    Нет, [[ cmd`]] `это не одно и то же. Значение false, если вывод команды пустой, и true в противном случае, независимо от состояния выхода команды.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню