«set -e» не завершает скрипт при возникновении ошибки в условном операторе

«set -e» не завершает скрипт при возникновении ошибки в условном операторе

В следующем скрипте есть синтаксическая ошибка или какая-то другая ошибка:

#!/usr/bin/env bash
set -euo pipefail

if [ ! -f /custom.log]; then
  echo "test"
fi
abcxyz

Скрипт завершается ошибкой, выводится:

./test.sh: line 4: [: missing `]'
./test.sh: line 7: abcxyz: command not found

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

решение1

set -eне срабатывает при неудачном выполнении команд, которые используются в качестве условий, например, в разделе условий конструкций if/ while/ untilили слева от ||, &&или в функциях, подоболочках, исходных файлах, evaled-коде, которые вызываются при этих условиях.

Если это так, то:

if [ ! -f /custom.log ]; then

/custom.logЕсли бы это был обычный файл, скрипт бы вышел из работы , поскольку [тогда он также вышел бы с ненулевым статусом выхода.

Встроенная [команда оболочки bash(и большинства других реализаций) завершается со 1статусом, если проверяемое условие не выполнено, а также 2если обнаружена синтаксическая ошибка (хотя это не все синтаксические ошибки, например, не в [ -v 'a[+]' ]).POSIX требует, чтобы в случае ошибки статус выхода был больше 1..

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

shopt -s extdebug # make sure the DEBUG trap propagates to subshells
trap '(($?>1 && (ret=$?))) && exit "$ret"' DEBUG
[ -f / ] || echo / not a regular file # OK
[ -f /] || echo was a syntax error # causes an exit, not output
echo not reached

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

Теперь остерегайтесь последствий. Например, это может вызвать:

if grep -qs pattern /file; then
  echo pattern was found in /file
fi

для выхода, если /fileне существует или не может быть прочитан, поскольку grepв этом случае возвращается статус 2, хотя с -s, намерение явно состояло в том, чтобы игнорировать эти случаи.

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

if sh -c 'grep -sq pattern / file || exit 1'; then...

Вы могли бы ограничитьвыход при статусе выхода больше 1к команде [or testчто-то вроде:

unset -v previous_BASH_COMMAND
trap '
  case $previous_BASH_COMMAND in
    ("[ "* | "test "*) (($?>1 && (ret=$?))) && exit "$ret"
  esac
  previous_BASH_COMMAND=$BASH_COMMAND' DEBUG

Это имеет несколько ограничений.

echo x
([ -f/]; echo y)

Это приведет к выходу из подоболочки, но не из родительской, поскольку $previous_BASH_COMMANDтам не было установлено. И в:

[ -f / ] && echo a regular file
(grep -qs foo /file && echo foo in /file)
echo here

Оболочка будет закрыта при запуске echo here, поскольку $? будет 2 и $previous_BASH_COMMANDбудет [ -f / ].

В любом случае, такие вещи, как

[ -f /] | cat
export var="$([ -f /])"

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

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

Связанный контент