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

437

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

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

например

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

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

Ответы:

742

Добавьте это в начало сценария:

set -e

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

Смотрите страницу руководства bash (1) во внутренней команде set для более подробной информации.

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

Вилле Лаурикари
источник
36
Это бы сработало, но мне нравится использовать "#! / Usr / bin / env bash", потому что я часто запускаю bash не из / bin. И "#! / Usr / bin / env bash -e" не работает. Кроме того, приятно иметь место для изменения, чтобы читать «set -xe», когда я хочу включить трассировку для отладки.
Ville Laurikari
48
Кроме того, флаги в строке shebang игнорируются, если скрипт запускается как bash script.sh.
Том Андерсон
27
Просто примечание: если вы объявляете функции внутри bash-скрипта, функция должна быть повторно объявлена ​​как set -e в теле функции, если вы хотите расширить эту функциональность.
Джин Ким
8
Кроме того, если вы создадите свой сценарий, строка Шебанга будет бесполезной.
4
@JinKim Похоже, что это не так в Bash 3.2.48. Попробуйте следующее внутри скрипта: set -e; tf() { false; }; tf; echo 'still here'. Даже без set -eвнутренней части tf(), выполнение отменяется. Возможно, вы хотели сказать, что set -eне наследуется подоболочками , что является правдой.
mklement0
202

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

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

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

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

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

Завтра вы делаете, казалось бы, тривиальные изменения:

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

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

#! / Bin / Баш
установить -е 
set -o pipefail

./configure | Тройник configure.log
делать
leonbloy
источник
1
Спасибо за объяснение важности того, pipefailчтобы идти вместе с set -o!
Малкольм
83

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

dosomething1 || exit 1

Если вы прислушиваетесь к совету Вилле Лаурикари и используете его, set -eто для некоторых команд вам может понадобиться следующее:

dosomething || true

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

Зан Рысь
источник
1
Мне это нравится. Тем более, что главный ответ - bash-centric (мне совершенно не ясно, относится ли / в какой степени к сценариям zsh). И я мог бы это посмотреть, но ваша просто понятнее, потому что логика.
g33kz0r
set -eне является bash-centric - он поддерживается даже на оригинальной Bourne Shell.
Маркос Вивес дель Соль
27

Если у вас есть очистка, которую вы должны сделать при выходе, вы также можете использовать 'trap' с псевдосигналом 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; Ваша ловушка будет выполняться, когда скрипт завершит работу по любой причине, включая нормальное завершение, прерывания, завершение, вызванное параметром -e, и т. д.

fholo
источник
12

$?Переменная редко. Псевдо-идиома 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
Марк Эдгар
источник
3
Почему «всегда должно быть написано как»? Я имею в виду, почему «так» должно быть? Когда команда длинная (например, вызывая GCC с дюжиной опций), тогда гораздо удобнее выполнить команду перед проверкой состояния возврата.
2012 г.
Если команда слишком длинная, вы можете разбить ее, назвав ее (определите функцию оболочки).
Марк Эдгар
12

Запустите его с -eили set -eнаверху.

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

lumpynose
источник
34
Чтобы потенциально спасти других, нужно прочитать help set: -uобрабатывает ссылки на неустановленные переменные как ошибки.
mklement0
1
так или нет, set -uили set -eне оба? @lumpynose
ericn
1
@eric Я уволился несколько лет назад. Хотя я любил свою работу, мой старый мозг все забыл. Я бы догадался, что вы могли бы использовать оба вместе; плохая формулировка с моей стороны; Я должен был сказать «и / или».
lumpynose
3

Выражение как

dosomething1 && dosomething2 && dosomething3

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

cat nosuchfile && echo "done"
echo $?
1
Габор
источник
2
#!/bin/bash -e

должно хватить.

Бали Уддин
источник
-2

просто добавлю еще один для справки, поскольку у Марка Эдгарса был дополнительный вопрос, и вот еще один пример, который затрагивает общую тему:

[[ `cmd` ]] && echo success_else_silence

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

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

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
источник
5
Нет, [[ cmd`]] `это не одно и то же. Значение false, если вывод команды пустой, и true в противном случае, независимо от состояния выхода команды.
Жиль "ТАК - перестань быть злым"