Что означает set -e в скрипте bash?

714

Я изучаю содержимое этого файла preinst, который скрипт выполняет перед тем, как этот пакет будет распакован из файла архива Debian (.deb).

Скрипт имеет следующий код:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Мой первый запрос о строке:

set -e

Я думаю, что остальная часть сценария довольно проста: он проверяет, выполняет ли менеджер пакетов Debian / Ubuntu операцию установки. Если это так, он проверяет, было ли мое приложение только что установлено в системе. Если это так, скрипт выводит сообщение «MyApplicationName только что установлено» и завершается ( return 1что означает «ошибка», не так ли?).

Если пользователь просит систему пакетов Debian / Ubuntu установить мой пакет, сценарий также удаляет две директории.

Это правильно или я что-то упустил?

AndreaNobili
источник
42
set -e
Андерс Линдал
46
причина, по которой вы не можете найти это в Google: -e в вашем запросе интерпретируется как отрицание. Попробуйте следующий запрос: bash set "-e"
Maleev
3
@twalberg Когда я задавал себе тот же вопрос, я смотрелman set
Седат
4
если вы ищете, как его отключить, поменяйте тире на префикс плюс:set +e
Tom Saleeba
@ twalberg, но спрашивать реальных людей гораздо интереснее, чем просто сделать запрос от робота ;-).
августа

Ответы:

798

От help set:

  -e  Exit immediately if a command exits with a non-zero status.

Но некоторые считают это плохой практикой (часто задаваемые вопросы по bash и irc freenode #bash часто задаваемые вопросы). Рекомендуется использовать:

trap 'do_something' ERR

запустить do_somethingфункцию при возникновении ошибок.

Смотрите http://mywiki.wooledge.org/BashFAQ/105

Жиль Квено
источник
14
Что было бы с do_something, если бы я хотел использовать ту же семантику, что и «Выход немедленно, если команда завершается с ненулевым статусом»?
CMCDragonkai
71
trap 'exit' ERR
chepner
12
ERRЛовушка не наследуется оболочки функций, поэтому если у вас есть функции, set -o errtraceили set -Eпозволит вам просто установить ловушку раз и применять его во всем мире.
ykay
31
это trap 'exit' ERRделать что - нибудь отличается от set -e?
Энди
22
если это плохая практика, то почему он используется в пакетах Debian ?
phuclv
98

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

Робин Грин
источник
45
Он останавливает выполнение, только если последняя команда в конвейере имеет ошибку. Существует специальная опция Bash, set -o pipefailкоторую можно использовать для распространения ошибок, чтобы возвращаемое значение команды конвейера отличалось от нуля, если одна из предыдущих команд вышла с ненулевым статусом.
Энтони Геогеган
2
Имейте в виду, что это -o pipefailозначает только то, что статус выхода первой ненулевой (то есть ошибочной в -o errexitтерминах) команды конвейера распространяется до конца. Остальные команды в конвейере по- прежнему выполняются , даже с set -o errexit. Например:, echo success | cat - <(echo piping); echo continuesгде echo successпредставляет успешную, но ошибочную команду, напечатает success, pipingи continues, но false | cat - <(echo piping); echo continues, с falseпредставлением команды, теперь с ошибкой молчащей, все равно будет печатать pipingперед выходом.
bb010g
55

Согласно bash - в руководстве Set Builtin , если -e/ errexitустановлено, оболочка немедленно завершается, если конвейер, состоящий из одной простой команды , списка или составной команды, возвращает ненулевой статус.

По умолчанию состояние выхода конвейера является состоянием выхода последней команды в конвейере, если эта pipefailопция не включена (по умолчанию она отключена).

Если это так, состояние возврата конвейера последней (самой правой) команды для выхода с ненулевым статусом или ноль, если все команды завершаются успешно.

Если вы хотите выполнить что-то при выходе, попробуйте определить trap, например:

trap onexit EXIT

где onexitваша функция, чтобы сделать что-то при выходе, как показано ниже, которая печатает простую трассировку стека :

onexit(){ while caller $((n++)); do :; done; }

Существует аналогичная опция -E/,errtrace которая вместо этого будет ловить ERR, например:

trap onerr ERR

Примеры

Пример нулевого статуса:

$ true; echo $?
0

Пример ненулевого статуса:

$ false; echo $?
1

Примеры отрицательного статуса:

$ ! false; echo $?
0
$ false || true; echo $?
0

Тест с pipefailотключением:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Тест с pipefailвключенным:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
kenorb
источник
55

Я нашел этот пост, пытаясь выяснить, каков был статус выхода для сценария, который был прерван из-за set -e. Ответ не казался мне очевидным; отсюда и этот ответ. По сути, set -eпрерывает выполнение команды (например, сценарий оболочки) и возвращает код состояния завершения команды, которая завершилась неудачей (т. Е. Внутренний сценарий, а не внешний сценарий) .

Например, предположим, у меня есть сценарий оболочки outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

Код для inner-test.sh:

#!/bin/sh
exit 26;

Когда я запускаю outer-script.shиз командной строки, мой внешний скрипт завершается с кодом выхода внутреннего скрипта:

$ ./outer-test.sh
$ echo $?
26
entpnerd
источник
10

Я полагаю, что цель сценария - быстро провалиться.

Чтобы проверить это самостоятельно, просто введите set -eв командной строке bash. Теперь попробуйте запустить ls. Вы получите список каталогов. Теперь введите lsd. Эта команда не распознается и возвращает код ошибки, поэтому ваша подсказка bash закроется (из-за set -e).

Теперь, чтобы понять это в контексте «скрипта», используйте этот простой скрипт:

#!/bin/bash 
# set -e

lsd 

ls

Если вы запустите его как есть, вы получите список каталогов из lsпоследней строки. Если вы раскомментируете set -eи запускаете снова, вы не увидите список каталогов, так как bash прекращает обработку, когда обнаруживает ошибку от lsd.

Каллин Нагельберг
источник
Добавляет ли этот ответ какую-либо информацию или информацию, которая еще не была предоставлена ​​другим по этому вопросу?
Чарльз Даффи
6
Я думаю, что он предлагает четкое, краткое объяснение функциональности, которого нет в других ответах. Ничего дополнительного, просто более сфокусированного, чем другие ответы.
Каллин Нагельберг
9

Это старый вопрос, но ни один из ответов здесь не обсуждает использование set -eaka set -o errexitв сценариях обработки пакетов Debian. Использование этой опции обязательно в этих сценариях согласно политике Debian; очевидно, что цель состоит в том, чтобы избежать любой возможности необработанного условия ошибки.

На практике это означает, что вы должны понимать, при каких условиях выполняемые вами команды могут возвращать ошибку, и явно обрабатывать каждую из этих ошибок.

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

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Обратите также внимание на то, как мы позаботимся о том, чтобы имя текущего скрипта включалось в сообщение и записывали диагностические сообщения в стандартную ошибку вместо стандартного вывода.)

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

diff this that || true
grep cat food || :

(Использование команды оболочки :no-op немного неясно, но довольно часто встречается.)

Просто чтобы повторить,

something || other

это сокращение для

if something; then
    : nothing
else
    other
fi

то есть мы явно говорим, что otherдолжен быть запущен тогда и только тогда, когда somethingпроизойдет сбой. Стенограмма if(и другие операторы управления потоком оболочки, такие как while, until) также является допустимым способом обработки ошибки (в самом деле, если бы это было не так, сценарии оболочки с такими параметрами set -eникогда не могли бы содержать операторы управления потоком!)

А также, чтобы быть в явном виде, в отсутствие такого обработчика, это set -eможет привести к немедленному отказу всего сценария с ошибкой, если будет diffнайдено различие или если grepне найдено совпадение.

С другой стороны, некоторые команды не выдают состояние выхода из ошибки, когда вы этого хотите. Обычно проблемными являются команды find(состояние выхода не отражает, были ли файлы фактически найдены) и sed(состояние выхода не показывает, получил ли скрипт какой-либо ввод или действительно выполнил какие-либо команды успешно). В некоторых случаях простой защитой является передача по команде, которая кричит, если нет вывода:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Следует отметить, что состояние выхода конвейера является состоянием выхода последней команды в этом конвейере. Таким образом, вышеприведенные команды на самом деле полностью маскируют статус findи sedи только сообщают вам, grepнаконец-то, успешно ли это выполнено .

(Bash, конечно, имеет set -o pipefail; но сценарии пакета Debian не могут использовать функции Bash. Политика твердо диктует использование POSIX shдля этих сценариев, хотя это не всегда имело место.)

Во многих ситуациях это то, на что следует обращать особое внимание при кодировании с защитой. Иногда вам нужно, например, просмотреть временный файл, чтобы увидеть, успешно ли завершилась команда, создавшая этот вывод, даже если в противном случае идиома и удобство заставили бы вас использовать конвейер оболочки.

tripleee
источник
это отличный ответ. и это способствует лучшей практике. У меня была точно такая же проблема с командой GREP, и я действительно не хотел удалять 'set -e'
Минни
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Маникандан Радж
источник