"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, но не нашел подходящего ответа.
Ответы
Ответ 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, отличного от последнего.