Що означає набір -e в баш-скрипті?


713

Я вивчаю зміст цієї престінги файлу який виконує скрипт до того, як цей пакунок буде розпакований з його архіву Debian archive (.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 встановити мій пакет, сценарій також видаляє два каталоги.

Це правильно чи я щось пропускаю?



46
Причина, чому ви не можете знайти це в google: -e у вашому запиті трактується як заперечення. Спробуйте наступний запит: bash set "-e"
Малєєв

3
@twalberg Коли я задав собі те саме питання, я дививсяman set
Sedat Kilinc

4
якщо ви шукаєте, як його вимкнути, поміняйте тире на плюс-префікс:set +e
Tom Saleeba

@twalberg, але запитувати реальних людей набагато цікавіше, ніж просто робити запит від робота ;-).
вдегенне

Відповіді:


797

Від help set:

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

Але деякі вважають поганою практикою (автори bash FAQ та irc freenode #bash FAQ). Рекомендується використовувати:

trap 'do_something' ERR

запускати do_somethingфункцію при виникненні помилок.

Дивіться http://mywiki.wooledge.org/BashFAQ/105


14
Що було б щось робити, якби я хотів ту саму семантику, як "Вихід негайно, якщо команда закінчується з ненульовим статусом"?
CMCDragonkai

71
trap 'exit' ERR
чепнер

12
ERRПастка не успадковується оболонки функцій, тому якщо у вас є функції, set -o errtraceабо set -Eдозволить вам просто встановити пастку раз і застосовувати його в усьому світі.
добре

31
чи trap 'exit' ERRробить щось інше, ніж set -e?
Енді

22
якщо це погана практика, то чому він використовується в пакунках Debian ?
phuclv

98

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


44
Він зупиняє виконання лише в тому випадку, якщо в останній команді в конвеєрі є помилка. Існує специфічний варіант 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

54

Відповідно до 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

54

Я знайшов цю публікацію, намагаючись розібратися, який статус виходу для сценарію, який був перерваний через a 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

10

Я вважаю, що задуманий сценарій швидко провалиться.

Щоб перевірити це самостійно, просто введіть set -ebash-підказку. Тепер спробуйте запустити ls. Ви отримаєте список каталогу. Тепер наберіть lsd. Ця команда не розпізнається і повертає код помилки, і тому ваше запит bash закриється (черезset -e ).

Тепер, щоб зрозуміти це в контексті "сценарію", використовуйте цей простий скрипт:

#!/bin/bash 
# set -e

lsd 

ls

Якщо запустити його таким, яким він є, ви отримаєте перелік каталогів з lsостаннього рядка. Якщо ви відмените команду set -eта запустіть її знову, ви не побачите в списку каталогів, оскільки bash припиняє обробку, як тільки зіткнеться з помилкою lsd.


Чи додає ця відповідь будь-яке розуміння чи інформацію, яка вже не була надана іншими питаннями?
Чарльз Даффі

6
Я думаю, що це пропонує чітке, коротке пояснення функціональності, якого немає в інших відповідях. Нічого додаткового, просто більш зосереджений, ніж інші відповіді.
Каллін Нагельберг

9

Це старе запитання, але жоден з відповідей тут не обговорює використання set -eака 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 || :

(Використання оболонок : дещо незрозуміле, але досить часто зустрічається.)

Просто ще раз,

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. Політика твердо диктує використання POSIXsh для цих сценаріїв, хоча це не завжди було так.)

У багатьох ситуаціях на це потрібно окремо стежити, коли кодує оборонно. Іноді доводиться, наприклад, пройти тимчасовий файл, щоб ви могли побачити, чи успішно закінчилася команда, яка створила цей результат, навіть коли ідіома та зручність інакше спрямовували б вас використовувати конвеєр оболонки.


це відмінна відповідь. і це сприяє кращій практиці. У мене була така ж проблема з команди GREP, і я дійсно не хотів видаляти 'set -e'
Minnie

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.