У меня есть простой сценарий оболочки со следующей преамбулой:
#!/usr/bin/env bash
set -eu
set -o pipefail
У меня также есть следующая функция:
foo() {
printf "Foo working... "
echo "Failed!"
false # point of interest #1
true # point of interest #2
}
Выполнение foo()
как обычной команды работает так, как ожидалось: сценарий завершается в #1
, потому что код возврата false
не равен нулю, а мы используем set -e
.
Моя цель — зафиксировать вывод функции foo()
в переменной и распечатать его только в случае возникновения ошибки во время выполнения foo()
. Вот что я придумал:
printf "Doing something that could fail... "
if a="$(foo 2>&1)"; then
echo "Success!"
else
code=$?
echo "Error:"
printf "${a}"
exit $code
fi
Сценарий не завершается в #1
, и выполняется путь "Success!"
оператора if
. Комментирование true
в #2
приводит к выполнению пути "Error:"
оператора if
.
Кажется, что bash просто игнорирует set -e
внутри подстановки, а оператор if
просто проверяет код возврата последней команды в foo()
.
В: Что вызывает такое странное поведение?
A: Именно так работает bash, это нормальное поведение.
В: Есть ли способ заставить bash уважать set -e
внутри подстановки команд и сделать так, чтобы это работало правильно?
A: Вы не должны использовать set -e
для этой цели.
В: Как бы вы реализовали это без set -e
(т. е. печатать вывод функции только в том случае, если что-то пошло не так во время ее выполнения)?
A: См. принятый ответ и мой раздел «последние мысли».
Я использую:
GNU bash, версия 5.0.11(1)-выпуск (x86_64-apple-darwin18.6.0)
Заключительные мысли/выводы (может быть полезным для кого-то еще):
Имейте в виду, что использование if ...; then
или даже && ... || ...
отключит большинство "традиционных" методов обработки ошибок bash (включая set -e
и trap ... ERR
+ set -o errtrace
) преднамеренно. Если вы хотите сделать что-то вроде того, что сделал я, вам, вероятно, следует вручную проверить коды возврата внутри вашей функции и вручную вернуть ненулевой код выхода (dangerous_command || return 1
), чтобы избежать продолжения выполнения при ошибках (вы можете сделать это независимо от того, используете ли вы set -e
или нет).
Как указано в ответе, set -e
не распространяется внутри подстановок команд по дизайну. Если вы хотите реализовать логику обработки ошибок, вы можете использовать trap ... ERR
в сочетании с set -o errtrace
, которые будут работать с функциями, работающими внутри подстановок команд (то есть, если вы не поместите их в оператор if
, который также отключит trap ... ERR
, поэтому в в этом случае ручная проверка кода возврата — ваш единственный вариант, если вы хотите остановить свою функцию при ошибках).
Если подумать, все это поведение имеет смысл: вы не ожидаете, что ваш сценарий завершится по команде, «защищенной» оператором if
, так как весь смысл вашего оператора if
проверяет, успешно ли выполнена команда или нет. .
Лично я все равно не стал бы полностью избегать set -e
и trap ... ERR
, поскольку они могут быть действительно полезными, но важно понимать, как они ведут себя в различных обстоятельствах, потому что они тоже не панацея.
-e
в разделе gnu.org/software/bash/ manual/bash.html#The-Set-Builtin - person glenn jackman   schedule 14.11.2019set -e
? А: просто не надо - person William Pursell   schedule 14.11.2019set -e
. Он ведет себя так, как задумано. К сожалению, то, как он спроектирован, не позволяет вам использовать его так, как вы хотите. Это кладж на пластыре. Просто не используйте его. - person William Pursell   schedule 14.11.2019false
неreturn false
. Вы возвращаете ноль, поэтому родительская оболочка считает, что ваша функция выполнена успешно. - person KamilCuk   schedule 14.11.2019