Назад | Перейти на главную страницу

Что делает set -e и почему это можно считать опасным?

Этот вопрос появился в викторине перед собеседованием, и это сводит меня с ума. Может ли кто-нибудь ответить на этот вопрос и успокоить меня? В викторине нет ссылки на конкретную оболочку, но описание работы предназначено для unix sa. опять вопрос просто ...

Что делает set -e и почему это можно считать опасным?

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 и вместо этого проверять результат каждой команды отдельно.

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

Из 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.

К сожалению, я недостаточно изобретателен, чтобы думать о том, почему это было бы опасно, кроме «остальная часть скрипта не будет выполнена» или «возможно, это может скрыть реальные проблемы».

Следует отметить, что 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

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

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

Итак, есть некоторые условия, при которых ненулевой статус не приведет к выходу.

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

Пожалуйста, также смотрите BashFAQ / 105 Почему set -e (или set -o errexit, или trap ERR) не делает того, что я ожидал?

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

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

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

Или, может быть, это вопрос с подвохом, который был бы неубедительным, но не совсем неожиданным.

Или, может быть, это хорошее объяснение: http://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg473314.html

set -e сообщает bash в сценарии о завершении работы, когда что-либо возвращает ненулевое возвращаемое значение.

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

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

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

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

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

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

  • set -e
    x=0
    let x++
    echo "x is $x"
    Вышеуказанное приведет к преждевременному завершению работы сценария оболочки, потому что let x++ возвращает 0, что обрабатывается let ключевое слово как ложное значение и превратилось в ненулевой код выхода. set -e замечает это и молча завершает сценарий.
  • set -e
    [ -d /opt/foo ] && echo "Warning: foo is already installed. Will overwrite." >&2
    echo "Installing foo..."
    

    Вышеуказанное работает, как ожидалось, выводит предупреждение, если /opt/foo уже существует.

    set -e
    check_previous_install() {
        [ -d /opt/foo ] && echo "Warning: foo is already installed. Will overwrite." >&2
    }
    
    check_previous_install
    echo "Installing foo..."
    

    Вышеупомянутое, несмотря на единственную разницу в том, что одна строка была преобразована в функцию, завершится, если /opt/foo не существует. Это потому, что тот факт, что он работал изначально, является особым исключением из set -eповедение. когда a && b возвращает ненулевое значение, игнорируется set -e. Однако теперь, когда это функция, код выхода функции равен коду выхода этой команды, и функция, возвращающая ненулевое значение, молча завершит скрипт.

  • set -e
    IFS=$'\n' read -d '' -r -a config_vars < config
    

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

    Остерегайтесь пользователей Sublime Text (и других текстовых редакторов, которые неправильно обрабатывают символы новой строки).

  • set -e
    
    should_audit_user() {
        local group groups="$(groups "$1")"
        for group in $groups; do
            if [ "$group" = audit ]; then return 0; fi
        done
        return 1
    }
    
    if should_audit_user "$user"; then
        logger 'Blah'
    fi
    

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

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

В качестве более конкретного примера ответа @Marcin, который лично меня укусил, представьте, что был rm foo/bar.txt строка где-нибудь в вашем скрипте. Обычно ничего страшного, если foo/bar.txt на самом деле не существует. Однако с set -e представьте сейчас ваш скрипт завершится раньше срока! Ой.