%3F.png)
errexit ( set -e
) часто предлагается как способ сделать простые скрипты более надежными. Однако, как правило, он рассматривается как ужасная ловушка всеми, кто видел, как он ведет себя в более сложных скриптах, в частности с функциями. И очень похожая проблема может быть замечена с подоболочками. Возьмем следующий пример, адаптированный изhttps://stackoverflow.com/questions/29926013/exit-subshell-on-error
(
set -o errexit
false
true
) && echo "OK" || echo "FAILED";
Ловушка здесь в том, что оболочки показывают «OK», а не «FAILED».[*]
Хотя поведение set -e
в конкретных случаях исторически менялось к сожалению, оболочки, доступные в современных дистрибутивах, утверждают, что следуют стандарту оболочки POSIX, и тестирование показало одинаковое поведение («OK») для всех следующих случаев:
bash-4.4.19-2.fc28.x86_64
(Федора)busybox-1.26.2-3.fc27.x86_64
(Федора)dash 0.5.8-2.4
(Дебиан 9)zsh 5.3.1-4+b2
(Дебиан 9)posh 0.12.6+b1
(Дебиан 9)ksh 93u+20120801-3.1
(Дебиан 9).
Вопрос
Есть ли какая-то техническая причина, по которой вы не можете написать оболочку с функциями, аналогичными POSIX
sh
, за исключением того, что она печатаетFAILED
для приведенного выше кода?Я надеюсь, что это также изменится
set -e
, и выражения верхнего уровня&&
, возвращающие false, будут считаться фатальными. https://serverfault.com/a/847016/133475 И, надеюсь, он сможет использовать более приятную идиому с grep.set -e # print matching lines, but it's not an error if there are no matches in the file ( set +e grep pattern file.txt RET=$? [[ $RET == 0 || $RET == 1 ]] || exit $RET )
Я также предполагаю, что арифметические операторы (
let
встроенные) будут переопределены каким-то образом, чтобы избежать неявного приведения их числового значения к статусу выхода true/false.Есть ли пример оболочки, которая делает что-либо из этого?
Я не против взглянуть на что-то с другим синтаксисом, нежели Bourne. Мне интересно что-то компактное, при написании коротких "связующих" скриптов, но также имеющее компактную стратегию обнаружения ошибок. Мне не нравится использовать
&&
в качестве разделителя операторов, если это означает, что случайный пропуск разделителя операторов приведет к молчаливому игнорированию ошибок.
[*] EDIT. Это, возможно, не идеальный пример для использования. Обсуждение предполагает, что лучшим примером будет перемещение set -o errexit
выше подоболочки.
AFAICT это очень похоже на тестовый пример (false; echo foo) || echo bar; echo \ $?
из таблицыздесь, который говорит, что он покажет "foo" (а затем "0") в оригинальной оболочке Bourne и большинстве ее потомков. Исключениями являются "hist.ash" и bash 1.14.7.
При добавлении set -e
внутрь подоболочки - (set -e; false; echo foo) || echo bar; echo \ $?
- появляются два дополнительных исключения: «SVR4 sh sun5.10» и dash-0.3.4.
Для сути моего вопроса, я полагаю, что наличие set -e
внутри подоболочки было отвлечением. Мой главный интерес заключается в идиоме, где вы используете set -e
в верхней части скрипта, и это также применимо к подоболочкам (что уже является поведением POSIX).
Йорг Шиллинг предполагает, что оболочка Bourne из оригинального Unix вывела бы «FAILED» для примера, который я использовал в этом вопросе, и что он портировал эту оболочку в POSIX как"ош" в schilytools, и что этот результат был проверен на момент выпуска 2018-06-11. Возможно, это основано на 1) set -e
нахождении внутри подоболочки и 2) "SVR4 sh sun5.10". "osh" "основан на исходниках OpenSolaris и, таким образом, основан на SVR4 и SVID3". Или, возможно, есть какая-то ужасающая дополнительная дисперсия, вызванная добавлением && echo "OK"
между подоболочкой и || echo "FAILED"
.
Я не думаю, что оболочка schily отвечает на мой вопрос. Вы можете использовать подоболочки для функций (использовать (
вместо {
для запуска функции в подоболочке) и начинать каждую подоболочку с set -e
. Однако использование подоболочек вводит ограничение, заключающееся в том, что вы не можете изменять глобальные переменные. По крайней мере, я еще не видел, чтобы кто-то защищал это как универсальный стиль кодирования.
решение1
Theстандарт POSIX(см.-е) более понятен, чем руководство Bash по errexit
опции shell.
Если эта опция включена, то в случае сбоя любой команды (по любой из причин, перечисленных в разделе «Последствия ошибок оболочки» или при возврате статуса выхода больше нуля) оболочка немедленно завершает работу, как если бы она выполняла специальную встроенную утилиту выхода без аргументов, за исключением следующих случаев:
Сбой любой отдельной команды в многокомандном конвейере не должен приводить к выходу оболочки. Рассматривается только сбой самого конвейера.
Параметр -e будет игнорироваться при выполнении списка составных элементов, следующего за зарезервированными словами while, until, if или elif, конвейером, начинающимся с зарезервированного слова !, или любой командой списка AND-OR, кроме последней.
Если статус завершения составной команды, отличной от команды подоболочки, был результатом сбоя при игнорировании -e, то -e не будет применяться к этой команде.
Это требование применяется к среде оболочки и каждой среде подоболочки в отдельности. Например, в:
set -e; (false; echo one) | cat; echo two
команда false приводит к завершению работы подоболочки без выполнения echo one; однако echo two выполняется, поскольку статус завершения конвейера (false; echo one) | cat равен нулю.
Очевидно, что пример кода эквивалентен следующему псевдокоду.
( LIST; ) && COMMAND || COMMAND
Статус выходасписок равен нулю, потому что:
опция shell
errexit
игнорируется.статус возврата — это статус выхода последней команды, указанной всписок, здесь
true
.
Таким образом, выполняется вторая часть списка И: echo "OK"
.
решение2
Не ответ на вопрос «Кто-нибудь пробовал писать ...», но некоторые мысли о том, как это можно сделать. Интересно, может ли кто-нибудь дать отзыв.
Обычный подход в современных скриптовых решениях (Python, JavaScript и старый Perl, и подобные) заключается в использовании блоков try ... catch. Ожидаемое поведение — любая ошибка должна вызывать исключение. Такое поведение противоречит поведению *sh, согласно которому ошибки фиксируются в $?. Очевидно, что '-e' не смог сделать это правильно таким образом, чтобы это работало так, как ожидает большинство разработчиков. Нет необходимости здесь вдаваться в подробности, так как это уже подробно документировано.
Два возможных подхода:
- Создайте лучший вариант
set -e
: например,set -ocatcherr
с лучшей обработкой ошибок. - Добавить новую
try { list ; }
команду, следуя современным скриптовым решениям
`-ofailerr` можно определить следующим образом:
- Если какая-либо команда завершается неудачей вне потока управления (if/while/until), текущий контекст выполнения должен вернуть 1 (или выйти, если это блок верхнего уровня). Это изменение фактически заставит разработчика обеспечить обработку ошибок для любой команды, которая завершается неудачей, или явно пометить команду с ошибками, которые можно игнорировать.
- Работает для простых скриптов, эффективно прерывает работу при необработанной ошибке.
Подход try { list ; }
аналогичный, просто другой синтаксис.
Пример:
function foo {
cat $* > a.txt
wc -l a.txt
}
set -oerrfail
foo /non/existing/file # Will fail, without executing the "wc -l"
if foo /non/existing/file ; then
...
else
# This is the "catch" block
fi
С блоком try:
try {
foo /non/existing/file
...
} || { catch-block }
Фактически, попытка эквивалентна "set -oerrofail", запускающему список команд под 'if ! { ... } ; then { catch-block }