Подтвердить что ты не робот

"set -e" в функции

set -e (или script, начинающийся с #!/bin/sh -e) чрезвычайно полезен для автоматической бомбардировки, если есть проблема. Это избавляет меня от необходимости проверять ошибки каждой отдельной команды, которая может выйти из строя.

Как получить эквивалент этого внутри функции?

Например, у меня есть следующий script, который немедленно выходит из строя с сообщением об ошибке:

#!/bin/sh -e

echo "the following command could fail:"
false
echo "this is after the command that fails"

Вывод будет таким, как ожидалось:

the following command could fail:

Теперь я хотел бы обернуть это в функцию:

#!/bin/sh -e

my_function() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

if ! my_function; then
    echo "dealing with the problem"
fi

echo "run this all the time regardless of the success of my_function"

Ожидаемый результат:

the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function

Фактический выход:

the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function

(т.е. функция игнорирует set -e)

Предположительно, это ожидаемое поведение. Мой вопрос: как получить эффект и полезность set -e внутри функции оболочки? Я нашел тот же вопрос, который запрашивал вне Stack Overflow, но не нашел подходящего ответа.

4b9b3361

Ответ 1

Из документации set -e:

Если этот параметр включен, если простая команда не выполняется для любого из причины, перечисленные в Последствиях Shell Errors или возвращает статус выхода значение > 0 и не является частью составной список через некоторое время, пока, или ключевое слово, и не будет часть списка AND или OR и не является трубопровод, которому предшествует! зарезервированный слово, то оболочка должна немедленно выход.

В вашем случае false является частью конвейера, которому предшествует ! и часть if. Таким образом, решение состоит в том, чтобы переписать свой код так, чтобы он не был.

Другими словами, здесь нет ничего особенного в функциях. Попробуйте:

set -e
! { false; echo hi; }

Ответ 2

Это немного kludge, но вы можете сделать:

export -f f
if sh -ec f; then 
...

Это будет работать, если ваша оболочка поддерживает экспорт -f (bash).

Обратите внимание, что это не приведет к завершению script. Эхо после того, как false в f не будет выполняться, и тело операторов if, но после выполнения if.

Если вы используете оболочку, которая не поддерживает экспорт -f, вы можете получить семантику, которую вы хотите, запустив sh в функции:

f() { sh -ec '
  echo This will execute
  false
  echo This will not
  '
}

Ответ 3

В конце концов я пошел с этим, что, видимо, работает. Сначала я попробовал метод экспорта, но потом обнаружил, что мне нужно экспортировать каждую глобальную (постоянную) переменную, используемую script.

Отключить set -e, затем запустить вызов функции внутри подоболочки с включенным set -e. Сохраните статус выхода подоболочки в переменной, снова включите set -e, затем проверьте var.

f() { echo "a"; false;  echo "Should NOT get HERE"; }

# Don't pipe the subshell into anything or we won't be able to see its exit status
set +e ; ( set -e; f ) ; err_status=$?
set -e

## cleaner syntax which POSIX sh doesn't support.  Use bash/zsh/ksh/other fancy shells
if ((err_status)) ; then
    echo "f returned false: $err_status"
fi

## POSIX-sh features only (e.g. dash, /bin/sh)
if test "$err_status" -ne 0 ; then
    echo "f returned false: $err_status"
fi

echo "always print this"

Вы не можете запустить f как часть конвейера или как часть списка команд && из || (за исключением последней команды в трубе или списке) или как условие в if или while, или других контекстов, которые игнорируют set -e. Этот код также не может быть ни в одном из этих контекстов, поэтому, если вы используете его в функции, вызывающие абоненты должны использовать один и тот же метод подсчета/сохранения-выхода-состояния. Это использование set -e для семантики, аналогичное исключениям бросания/ловушки, на самом деле не подходит для общего использования, учитывая ограничения и трудно читаемый синтаксис.

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

Вы могли бы подумать, что следующее будет работать, но это не так:

if ! ( set -e; f );then    ##### doesn't work, f runs ignoring -e
    echo "f returned false: $?"
fi

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

Ответ 4

Вы можете напрямую использовать подоболочку в качестве определения своей функции и немедленно установить ее с помощью set -e. Это ограничило бы область set -e только для функции подоболочки и позже избежит переключения между set +e и set -e.

Кроме того, вы можете использовать присвоение переменной в тесте if, а затем повторить результат в дополнительном выражении else.

# use subshell for function definition
f() (
   set -exo pipefail
   echo a
   false
   echo Should NOT get HERE
   exit 0
)

# next line also works for non-subshell function given by agsamek above
#if ret="$( set -e && f )" ; then 
if ret="$( f )" ; then
   true
else
   echo "$ret"
fi

# prints
# ++ echo a
# ++ false
# a

Ответ 5

Объедините все команды в своей функции с оператором &&. Это не слишком много проблем и даст результат, который вы хотите.

Ответ 6

Я знаю, что это не то, что вы просили, но вы можете или не знать, что поведение, которое вы ищете, встроено в "make". Любая часть процесса "make", которая не позволяет выполнить прогон. Это совершенно другой способ "программирования", хотя сценарий оболочки.

Ответ 7

Вам нужно будет вызвать вашу функцию в подклассу (внутри скобок ()), чтобы достичь этого.

Я думаю, вы хотите написать свой script следующим образом:

#!/bin/sh -e

my_function() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

(my_function)

if [ $? -ne 0 ] ; then
    echo "dealing with the problem"
fi

echo "run this all the time regardless of the success of my_function"

Затем выход (по желанию):

the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function

Ответ 8

Это по дизайну и спецификации POSIX. Мы можем прочитать в man bash:

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

поэтому вам не следует полагаться на set -e внутри функций.

Учитывая следующий пример Austin Group:

set -e
start() {
   some_server
   echo some_server started successfully
}
start || echo >&2 some_server failed

set -e игнорируется внутри функции, потому что функция является командой в списке AND-OR, отличном от последнего.

Указанное поведение указано и требуется POSIX (см. Желаемое действие):

Параметр -e игнорируется при выполнении составного списка, следующего за зарезервированным словом while, until, if или elif, конвейер, начинающийся с зарезервированного слова !, или любой команда списка AND-OR, отличного от последнего.