Кто-нибудь пробовал написать оболочку, исправляющую errexit (`set -e`)?

Кто-нибудь пробовал написать оболочку, исправляющую errexit (`set -e`)?

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).

Вопрос

  1. Есть ли какая-то техническая причина, по которой вы не можете написать оболочку с функциями, аналогичными 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.

  2. Есть ли пример оболочки, которая делает что-либо из этого?

    Я не против взглянуть на что-то с другим синтаксисом, нежели 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.

Если эта опция включена, то в случае сбоя любой команды (по любой из причин, перечисленных в разделе «Последствия ошибок оболочки» или при возврате статуса выхода больше нуля) оболочка немедленно завершает работу, как если бы она выполняла специальную встроенную утилиту выхода без аргументов, за исключением следующих случаев:

  1. Сбой любой отдельной команды в многокомандном конвейере не должен приводить к выходу оболочки. Рассматривается только сбой самого конвейера.

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

  3. Если статус завершения составной команды, отличной от команды подоболочки, был результатом сбоя при игнорировании -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 }

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