Що робить "set -e", і чому це може вважатися небезпечним?


147

Це запитання з'явилось у вікторині перед інтерв'ю, і це мене зводить з розуму. Хтось може відповісти на це і полегшити мене? Тест не має посилання на певну оболонку, але опис завдання призначений для unix sa. знову питання просто ...

Що робить "set -e", і чому це може вважатися небезпечним?


Відповіді:


154

set -e викликає вихід оболонки, якщо будь-яка підкоманда чи конвеєр повертає ненульовий статус.

Відповідь, яку, мабуть, шукав інтерв'юер:

Було б небезпечно використовувати "set -e" під час створення сценаріїв init.d:

Від http://www.debian.org/doc/debian-policy/ch-opersys.html 9.3.2 -

Будьте обережні, використовуючи set -e в сценаріях init.d. Написання правильних сценаріїв init.d вимагає прийняття різних статусів виходу з помилок, коли демони вже запущені або вже зупинені, не перериваючи сценарій init.d, а загальні бібліотеки функцій init.d не безпечні для виклику із ефектом set -e. Для скриптів init.d часто простіше не використовувати set -e, а замість цього перевірити результат кожної команди окремо.

Це справедливе питання з точки зору інтерв'юера, оскільки він оцінює кандидатів робочі знання сценаріїв та автоматизації на рівні сервера


влучне зауваження. це зупинить процес завантаження через щось, що технічно може бути помилкою, але не повинно зупиняти весь сценарій
Метт,

Хоча я погоджуюся з stackoverflow.com/questions/78497/… , я думаю, що цей стиль добре підходить з bash для коротких перевірок init та ведення журналів чи іншого виправлення мавпи середовища перед тим, як виконувати цільове навантаження. Ми використовуємо цю саму політику для нашого зображення докера скрипти init.
joshperry

33

Від bash(1):

          -e      Exit immediately if a pipeline (which may consist  of  a
                  single  simple command),  a subshell command enclosed in
                  parentheses, or one of the commands executed as part  of
                  a  command  list  enclosed  by braces (see SHELL GRAMMAR
                  above) exits with a non-zero status.  The shell does not
                  exit  if  the  command that fails is part of the command
                  list immediately following a  while  or  until  keyword,
                  part  of  the  test  following  the  if or elif reserved
                  words, part of any command executed in a && or  ││  list
                  except  the  command  following  the final && or ││, any
                  command in a pipeline but the last, or if the  command’s
                  return  value  is being inverted with !.  A trap on ERR,
                  if set, is executed before the shell exits.  This option
                  applies to the shell environment and each subshell envi-
                  ronment separately (see  COMMAND  EXECUTION  ENVIRONMENT
                  above), and may cause subshells to exit before executing
                  all the commands in the subshell.

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


1
маскують справжні / інші проблеми, так, я думаю, я міг бачити, що ... я намагався
привести

Там розміщена сторінка set! Я завжди закінчуюсь у builtin(1). Дякую.
Джаред Бек

як я можу знати, що документація setна підлягає знайденню man 1 bash?? і як хтось зможе знайти -eваріант для setключового слова на такій величезній сторінці керівництва? Ви не можете просто /-eшукати тут
Blauhirn

@Blauhirn usinghelp set
Blauhirn

19

Слід зазначити, що set -eдля різних розділів сценарію можна вмикати та вимикати. Це не повинно бути включеним для виконання всього сценарію. Це навіть можна умовно включити. При цьому я ніколи не використовую його, оскільки я роблю власні помилки (чи ні).

some code
set -e     # same as set -o errexit
more code  # exit on non-zero status
set +e     # same as set +o errexit
more code  # no exit on non-zero status

Також заслуговує на увазі це з розділу розділу чоловіка Баша в trapкоманді, який також описує set -eфункціонування за певних обставин.

Пастка ERR не виконується, якщо невдала команда є частиною списку команд відразу після деякого часу або до появи ключового слова, частина тесту в операторі if, частина команди, виконаної у && або ⎪⎪ списку, або якщо команда повернене значення інвертується через !. Це ті самі умови, яких дотримується варіант errexit.

Отже, є деякі умови, за яких ненульовий статус не спричинить вихід.

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

Будь ласка, дивіться також BashFAQ / 105 Чому б не встановити -e (або встановити -o errexit або trap ERR) не виконати те, що я очікував?


Трохи відхиляючись від запитання, ця відповідь саме те, що я шукаю: вимкнення його лише для невеликої частини сценарію!
Стефан Біцзіттер

13

Майте на увазі, що це вікторина для співбесіди. Питання, можливо, написали нинішні співробітники, і вони можуть бути помилковими. Це не обов'язково погано, і кожен робить помилки, а питання інтерв'ю часто сидять у темному кутку без огляду і виходять лише під час співбесіди.

Цілком можливо, що "set -e" не робить нічого, що ми вважали б "небезпечним". Але автор цього питання може помилково вважати, що "встановити -e" небезпечно через їх власне незнання або упередженість. Можливо, вони написали баггі-скрипт-скрипт, він жахливо бомбардував, і вони помилково подумали, що винна «set -e», коли насправді вони знехтували написати належну перевірку помилок.

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

Чи, може, це хитромудрі питання, які були б кульгавими, але не зовсім несподіваними.

А може, це хороше пояснення: http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg473314.html


1
+1 Я в одному човні, і багато "технічних" інтерв'ю, з якими я мав справу, були принаймні трохи помилковими.
cpbills

1
Ого. Добре знайти в списку debian. +1 за незнання питань щодо інтерв'ю. Я пам’ятаю, як один раз стверджував відповідь про відступ, оскільки я був на 100% впевнений, що я правий. Вони сказали, що ні. Я пішов додому і гуглив це, я мав рацію.
egorgry

9

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

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


Це цікаво. Я не думав про дозволи, відкриті в сценарії, який передчасно помирає, але я міг бачити, що це вважається небезпечним. Найцікавішою частиною вікторини є те, що ви не можете використовувати будь-які довідкові матеріали, такі як man або google, і якщо ви не можете повністю відповісти, не відповідайте на це взагалі.
egorgry

6
це просто нерозумно, я б переглянув цього роботодавця ... жартую (начебто). добре мати потужну базу знань, але половина роботи з ІТ - це знати / де / знайти інформацію ... сподіваємось, вони були досить розумні, щоб врахувати це при оцінці претендентів. на сторонній замітці, я бачу / ні / використовую для set -eнасправді, це говорить про лінь ігнорувати перевірку помилок у ваших сценаріях ... тож майте це на увазі і з цим роботодавцем ... якщо вони його використовують багато, як виглядають їхні сценарії, що вам неминуче доведеться підтримувати ...
cpbills

відмінна точка @cpbills. Я також не бачу користі для set -e в сценарії, і, мабуть, тому він так наткнувся на мене. Я хотів би опублікувати всі питання, оскільки деякі справді хороші, а деякі насправді хитрі і випадкові, як цей.
egorgry

Я бачив це в багатьох робочих місцях із системою Cron ...
Ендрю

8

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

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

Грег Wooledge має багато сказати про небезпеку set -e:

У другій ланці є різні приклади неінтуїтивної та непередбачуваної поведінки Росії set -e.

Деякі приклади неінтуїтивної поведінки set -e(деякі взяті з посилання wiki вище):

  • встановити -е
    x = 0
    нехай x ++
    відлуння "х є $ х"
    Вищезазначене спричинить передчасний вихід сценарію оболонки, оскільки let x++повертає 0, який трактується letключовим словом як хибне значення і перетворюється на ненульовий код виходу. set -eпомічає це і мовчки припиняє сценарій.
  • встановити -е
    [-d / opt / foo] && echo "Попередження: foo вже встановлено. Перезаписується." > & 2
    echo "Встановлення foo ..."
    

    Вищезазначене працює як очікувалося, друкуючи попередження, якщо воно /opt/fooвже існує.

    встановити -е
    check_previous_install () {
        [-d / opt / foo] && echo "Попередження: foo вже встановлено. Перезаписується." > & 2
    }
    
    check_previous_install
    echo "Встановлення foo ..."
    

    Вищезазначене, незважаючи на єдину різницю в тому, що один рядок було відновлено до функції, припиняється, якщо /opt/fooйого не існує. Це тому, що той факт, що він працював спочатку, є особливим винятком set -eз поведінки Росії. Коли a && bповертається ненульовим, він ігнорується set -e. Однак тепер, коли це функція, вихідний код функції дорівнює коду виходу цієї команди, і функція, що повертається ненульовою, буде мовчки припиняти сценарій.

  • встановити -е
    IFS = $ '\ n' read -d '' -r -a config_vars <config
    

    Вище буде прочитано масив config_varsз файлу config. Як автор може мати намір, він закінчується помилкою, якщо configйого немає. Як автор може не мати наміру, він мовчки припиняється, якщо configне закінчується новим рядком. Якщо б set -eтут не використовувались, то config_varsвони містили б усі рядки файлу, незалежно від того, закінчився він чи ні в новому рядку.

    Користувачі Sublime Text (та інші текстові редактори, які неправильно обробляють нові рядки), будьте обережні.

  • встановити -е
    
    should_audit_user () {
        локальні групові групи = "$ (групи" $ 1 ")"
        для групи в $ групах; робити
            if ["$ group" = аудит]; потім повернути 0; фі
        зроблено
        повернути 1
    }
    
    if повинен_audit_user "$ user"; тоді
        лісоруб 'Blah'
    фі
    

    Тут автор може обґрунтовано очікувати, що якщо з якихось причин користувача $userне існує, groupsкоманда буде відмовлена, і скрипт припиниться, замість того, щоб дозволити користувачеві виконувати якесь завдання без змін. Однак у цьому випадку set -eприпинення не набуває чинності. Якщо $userз якихось причин його не вдасться знайти, замість того, щоб закрити скрипт, should_audit_userфункція просто поверне невірні дані так, як ніби set -eне діяла.

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

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

Деякі додаткові недоліки set -e:

  • Це заохочує неохайний код. Обробники помилок повністю забуті, сподіваючись, що все, що не вдалося, повідомить про помилку якимось розумним чином. Однак у подібних прикладах let x++це не так. Якщо сценарій помирає несподівано, зазвичай це мовчки, що перешкоджає налагодженню. Якщо сценарій не вмирає, і ви очікували, що це (див. Попередню точку кулі), то у вас є більш тонка і підступна помилка на руках.
  • Це призводить людей до помилкового почуття безпеки. Побачте ще раз ifумову точки кулі.
  • Місця, де закінчується оболонка, не узгоджуються між оболонками або версіями оболонок. Можна випадково написати скрипт, який поводиться по-різному на старій версії bash через поведінку, set -eяка була перероблена між цими версіями.

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

set -e не є заміною освіти.


2

Я б сказав, що це небезпечно, тому що ви більше не контролюєте його надходження сценарію. Сценарій може закінчуватися до тих пір, поки будь-яка команда, до якої викликається сценарій, поверне не нуль. Отже, все, що вам потрібно зробити, - це зробити щось, що змінює поведінку або вихід будь-якого з компонентів, і ви можете припинити основний сценарій. Це може бути більше стильовою проблемою, але це, безумовно, має наслідки. Що робити, якщо цей головний сценарій повинен встановити прапор, а це не тому, що він достроково припинився? Якщо ви припустимо, що прапор повинен бути там, ви працюєте з несподіваним або старим значенням.


Повністю згоден з цією відповіддю. Це не властиво небезпечно, але це можливість мати несподіваний ранній вихід.
pboin

1
На що це мені нагадало - це мій професор C ++, який завжди тягнув точки з моїх завдань за те, що в програмі не було єдиної точки входу / однієї точки виходу. Я думав, що це суто принципова річ, але цей набір -однозначно демонструє, як і чому все може вийти з-під контролю. Зрештою, програми стосуються контролю та детермінізму, і передчасним припиненням ви відмовитесь від обох.
Марцін

0

Як конкретніший приклад відповіді @ Marcin, який особисто мене покусав, уявіть, що rm foo/bar.txtу вашому сценарії десь був рядок. Зазвичай нічого великого, якщо foo/bar.txtнасправді не існує. Однак з set -eтеперішнім часом ваш сценарій закінчиться достроково! На жаль

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