Я наблюдаю странное поведение при использовании set -e
( errexit
), set -u
( nounset
) вместе с ловушками ERR и EXIT. Они кажутся взаимосвязанными, поэтому разумно их объединить.
1) set -u
не запускает ERR-ловушки
Код:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- Ожидается: вызов ERR, RC! = 0
- Фактически: ERR-ловушка не вызывается , RC == 1
- Примечание:
set -e
не меняет результат
2) Использование set -eu
кода выхода в ловушке EXIT 0 вместо 1
Код:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- Ожидается: вызывается EXIT trap, RC == 1
- Фактически: ловушка EXIT называется, RC == 0
- Примечание. При использовании
set +e
RC == 1. Ловушка EXIT возвращает правильный RC, когда любая другая команда выдает ошибку. - Редактировать: есть SO сообщение на эту тему с интересным комментарием, предполагающим, что это может быть связано с используемой версией Bash. Тестирование этого фрагмента с помощью Bash 4.3.11 дает RC = 1, так что это лучше. К сожалению, в настоящее время обновление Bash (с версии 3.2.51) на всех хостах невозможно, поэтому нам нужно найти какое-то другое решение.
Кто-нибудь может объяснить любое из этих поведений?
Поиск по этим темам был не очень успешным, что довольно удивительно, учитывая количество постов в настройках и ловушках Bash. Хотя есть одна ветка форума , но вывод довольно неудовлетворительный.
bash
порвал со стандартом и начал ставить ловушки в подоболочки. Предполагается, что ловушка будет выполняться в той же среде, откуда пришло возвращение, ноbash
она этого не делала довольно давно.set -e
иset -u
оба предназначены специально для уничтожения скриптовой оболочки. Использование их в условиях, которые могут вызвать их приложение, приведет к уничтожению скриптовой оболочки. Нет ничего, кроме как не использовать их, а вместо этого проверять те условия, когда они применяются в последовательности кода. Таким образом, вы можете написать хороший шелл-код или использовать егоset -eu
.-u
бы не вызвать прерывание ERR (это ошибка, поэтому не следует запускать прерывание) или код ошибки 0 вместо 1. последнее похоже на ошибку, которая уже была исправлена в более поздней версии, вот и все. Но первую часть довольно сложно понять, если вы не поняли, что ошибки в оценке оболочки (расширение параметров) и реальные ошибки в командах кажутся двумя разными вещами. Что касается решения, ну, как вы предложили, я сейчас пытаюсь избежать-eu
и проверять вручную, когда это необходимо.(set -u; : $UNSET_VAR)
и тому подобное. Подобные вещи тоже могут быть хорошими - вы можете время от&&
времени отбрасывать множество вещей :(set -e; mkdir dir; cd dir; touch dirfile)
если вы поняли мой дрейф. Просто это контролируемые контексты - когда вы устанавливаете их как глобальные параметры, вы теряете контроль и становитесь контролируемыми. Однако обычно есть более эффективные решения.Ответы:
От
man bash
:set -u
"@"
и"*"
как ошибка при выполнении расширения параметра. Если попытка раскрытия выполняется для неустановленной переменной или параметра, оболочка выводит сообщение об ошибке и, если не является-i
интерактивной, завершает работу с ненулевым состоянием.POSIX заявляет, что в случае ошибки расширения неинтерактивная оболочка должна завершиться, когда расширение связано либо со специальной встроенной оболочкой (это различие
bash
регулярно игнорируется в любом случае, и поэтому, возможно, не имеет значения), либо с любой другой утилитой, кроме ,"${x!y}"
, потому что!
не является допустимым оператором) ; реализация может рассматривать их как синтаксические ошибки, если она способна обнаруживать их во время токенизации, а не во время расширения.Также из
man bash
:trap ... ERR
while
илиuntil
ключевого слова ...if
заявлении ...&&
или,||
за исключением команды, следующей за финальной&&
или||
...!
.-e
опция errexit .Обратите внимание, что ловушка ERR связана с оценкой возврата какой-либо другой команды. Но когда возникает ошибка раскрытия , не запускается команда для возврата чего-либо. В вашем примере этого
echo
не происходит - потому что, когда оболочка оценивает и расширяет свои аргументы, она встречает-u
переменную nset, которая была указана явным параметром оболочки, чтобы вызвать немедленный выход из текущей оболочки со сценариями.И поэтому выполняется ловушка EXIT , если таковая имеется, и оболочка завершает работу с диагностическим сообщением и выходит из состояния, отличного от 0 - именно так, как и должно быть.
Что касается rc: 0 , я ожидаю, что это ошибка определенного типа - вероятно, связанная с двумя триггерами для EXIT, возникающими в одно и то же время, и одним, получающим код выхода другого (который не должен возникать) . И в любом случае, с современным
bash
бинарным файлом, установленнымpacman
:Я добавил первую строку, чтобы вы могли видеть, что условия оболочки соответствуют условиям сценариев оболочки - она не является интерактивной. Выход:
Вот некоторые соответствующие заметки из недавних журналов изменений :
$?
неправильной установке асинхронных команд .for
командах, имели неправильный номер строки.trap
выполнялись в асинхронных командах подоболочки.trap
обработчиков для этих сигналов и позволяет рекурсивно запускать большинствоtrap
обработчиков (выполнениеtrap
обработчиков во время выполненияtrap
обработчика) .Я думаю, что это последнее или первое, что наиболее актуально - или, возможно, комбинация двух.
trap
Обработчик является по природе своей асинхронным , так как вся его работа состоит в том, чтобы ждать и обрабатывать асинхронные сигналы . И вы запускаете два одновременно с-eu
и$UNSET_VAR
.И поэтому, может быть, вам следует просто обновить, но если вы любите себя, вы сделаете это с другой оболочкой.
источник
(Я использую Bash 4.2.53). В первой части справочной страницы bash просто написано «Сообщение об ошибке будет записано со стандартной ошибкой, и неинтерактивная оболочка завершится». В нем не говорится, что будет вызвана ERR-ловушка, хотя я согласен, что было бы полезно, если бы это произошло.
Чтобы быть прагматичным, если вы действительно хотите более аккуратно справляться с неопределенными переменными, возможное решение состоит в том, чтобы поместить большую часть вашего кода в функцию, затем выполнить эту функцию в под-оболочке и восстановить код возврата и вывод stderr. Вот пример, где "cmd ()" является функцией:
На моем баш я получаю
источник