Что делает '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 небезопасны для вызова с установленным параметром -e. Для сценариев init.d часто проще не использовать set -e, а вместо этого проверять результат каждой команды отдельно.

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


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

Хотя я согласен со stackoverflow.com/questions/78497/… , я думаю, что этот стиль хорошо подходит для bash для коротких проверок инициализации и ведения журналов, а также для внесения исправлений в другие обезьяньи среды перед выходом из целевой рабочей нагрузки. Мы используем эту же политику для нашего образа докера сценарии инициализации.
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
маскировать реальные / другие проблемы, да, я думаю, я мог видеть, что ... я изо всех сил пытался придумать пример того, как это опасно, а также ...
cpbills

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

setОткуда мне знать, что документация должна быть найдена в man 1 bash?? и как кто-нибудь сможет найти -eопцию для setключевого слова на странице руководства, которая так огромна? Вы не можете просто /-eискать здесь
phil294

@Blauhirn используетhelp set
phil294

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

Также следует обратить внимание на раздел справочной страницы Bash trapкоманды, в котором также описывается, как set -eфункционирует при определенных обстоятельствах.

Ловушка ERR не выполняется, если сбойная команда является частью списка команд, следующих сразу за ключевым словом некоторое время или до, частью теста в операторе if, частью команды, выполняемой в списке && или ⎪⎪, или если команда возвращаемое значение инвертируется через! Это те же условия, которым подчиняется опция errexit.

Таким образом, существуют некоторые условия, при которых ненулевой статус не приведет к выходу.

Я думаю, что опасность состоит в том, что мы не понимаем, когда set -eвступает в игру, а когда нет, и неправильно полагаются на это в соответствии с каким-то неверным предположением.

См. Также BashFAQ / 105. Почему set -e (или set -o errexit, или trap ERR) не выполняет то, что я ожидал?


Немного отклоняясь от вопроса, этот ответ - именно то, что я ищу: отключить его только для небольшой части сценария!
Стефан Бийзиттер

13

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

Вполне возможно, что set -e ничего не делает, что мы считаем «опасным». Но автор этого вопроса может ошибочно полагать, что «set -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
это просто глупо, я бы пересмотрел этого работодателя ... шучу (вроде). хорошо иметь сильную базу знаний, но половина ИТ-специалистов знает / где / найти информацию ... надеюсь, они были достаточно умны, чтобы учесть это при оценке кандидатов. С другой стороны, я вижу / no / use для set -e, по сути, для меня это говорит о лени игнорировать проверку ошибок в ваших скриптах ... так что имейте это в виду, с этим работодателем тоже ... если они его используют много, как выглядят их сценарии, которые вам неизбежно придется поддерживать ...
cpbills

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

Я видел это во многих системных заданиях cron ...
Эндрю

8

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

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

Грег Вулледж может многое сказать об опасностях set -e:

Во второй ссылке есть различные примеры неинтуитивного и непредсказуемого поведения set -e.

Некоторые примеры не интуитивного поведения set -e(некоторые взяты из вики-ссылки выше):

  • установить -е
    х = 0
    пусть х ++
    echo "x is $ 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 () {
        local group groups = "$ (groups" $ 1 ")"
        для группы в $ groups; делать
            if ["$ group" = аудит]; затем верните 0; фи
        сделанный
        возврат 1
    }
    
    if should_audit_user "$ user"; тогда
        регистратор "Бла"
    фи
    

    Автор здесь может разумно ожидать, что если по какой-то причине пользователь $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.