Вопрос или проблема
В блоках Bash {} и подпроцессах () преждевременный выход не срабатывает, если за ним следует условие OR. Например, возьмите
set -e
{ echo a; false; echo b; } || echo c
выводит
a
b
и
set -e
{ echo a; false; echo b; false;} || echo c
выводит
a
b
c
Кажется, что учитывается только код выхода последней выполненной команды. Хотя это имеет смысл, учитывая, что используется точка с запятой вместо &&
, я ожидал, что set -e
все равно заставит выйти при первом false
и выполнит обработку ошибок echo c
.
Использование &&
вместо ;
действительно заставляет это работать, но это усложняет ситуацию, когда есть многострочные блоки. Добавление set -e
в начале блока/подпроцесса также не дает эффекта.
Причина, по которой это меня смущает, заключается в том, что
set -e
{ echo a; false; echo b; }
выводит
a
что означает, что выход при сбое работает, когда за ним не следует код ||
. Так что я ожидал, что это будет иметь место с кодом ||
после него, выполняя его после первого сбоя в блоке. Разве нет способа достичь этого без добавления &&
после каждой строки в блоке?
Ответ или решение
Вопрос, поднятый вами о поведении Bash в отношении выхода из блоков и подссылок при возникновении ошибки, действительно интересен и требует детального рассмотрения. Основная суть проблемы заключается в том, что команда set -e
, предназначенная для прерывания выполнения скрипта при возникновении ошибки, может вести себя иначе в блоках кода и при использовании логических операторов &&
и ||
. Давайте подробно разберем эту логику.
Логика работы set -e
Когда вы устанавливаете флаг set -e
, Bash прекращает выполнение скрипта, как только одна из команд возвращает ненулевой код возврата (что обычно указывает на ошибку). Однако это правило работает не всегда, особенно в контексте комбинирования команд с использованием логических операторов.
Рассмотрим ваш пример:
set -e
{ echo a; false; echo b; } || echo c
Здесь, несмотря на то что команда false
возвращает ненулевой код выхода и, казалось бы, должна остановить выполнение блока, Bash ведет себя иначе из-за конструкции с ||
. В этом случае Bash интерпретирует весь блок { ... }
как одну команду. Поскольку перед ||
имеется команда, которая, в данном случае, успешно завершается (то есть echo a
, которая успешно выводит ‘a’), enfin, set -e
не завершает выполнение скрипта после ошибки false
. Вместо этого, он обрабатывает код выхода блока и принимает его как успех, что в итоге приводит к тому, что команда echo b
также выполняется.
Аналогичная ситуация наблюдается в вашем следующем примере:
set -e
{ echo a; false; echo b; false;} || echo c
В этом случае команда false
в конце блока ничего не меняет, так как выполнение блока { ... }
уже завершилось, и её код не учитывается при интерпретации условия ||
.
Почему &&
работает иначе
Когда вы заменяете ||
на &&
, логика выполняется иначе:
set -e
{ echo a; false; echo b; } && echo c
Здесь, если блок { ... }
завершится с ошибкой, то echo c
не будет выполнена, потому что результат условия &&
будет ложным. Таким образом, после возникновения ошибки выполнение останавливается, и последующие команды не выполняются. Это и объясняет, почему добавление &&
создает более очевидное поведение при наличии ошибок.
Как добиться желаемого поведения
Если вы хотите, чтобы Bash возвращал в конце блока после ошибки, и выполнять последующий код, можно воспользоваться следующими подходами:
-
Использование команд
&&
вместо;
: как было показано выше, комбинирование с&&
будет корректно обрабатывать ошибки. -
Пространственные блоки: вы можете обернуть ваши команды в функцию и вызывать ее, что разрешает использование
set -e
:
set -e
my_function() {
echo a
false
echo b
}
my_function || echo c
В этом случае, когда команда false
возвращает ошибку, вы сможете корректно обработать её в ||
.
Заключение
Поведение set -e
в Bash может сбивать с толку, особенно в контексте блоков и логических операторов. Понимание того, как Bash интерпретирует коды выхода команд в закрытых блоках и подшеллах, поможет вам правильно строить свои скрипты. Будьте внимательны к тому, как вы используете логические операторы, и попробуйте оборачивать команды в функции, чтобы избежать неожиданных результатов в вашем коде.