Можно ли найти подход для выполнения set -e/ERR захвата подоболочки, невосприимчивого к && и || ограничения?

В то время как справочная страница Bash гласит:

Ловушка ERR не выполняется, если неудачная команда является ... частью команды, выполняемой в && или || список ...

Я надеялся, что код в подоболочке будет в другом контексте и не будет подпадать под вышеуказанное ограничение. Код ниже показывает, что даже подоболочки не застрахованы от этого ограничения:

#!/bin/bash

main()
{
   local Arg="$1"
   (
      set -e
      echo "In main: $Arg"
      trap MyTrap ERR
      $(exit 1)
      echo "Should not get here"
   )

   return 1
}

MyTrap()
{
   echo "In MyTrap"
}

main 1
[[ $? -eq 0 ]] || echo "failed"
echo
main 2 || echo "failed"

Приведенный выше код имеет следующий вывод:

In main: 1
In MyTrap
failed

In main: 2
Should not get here
failed

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

MyTrap()
{
   echo "_ERROR_=$?" > $HOME/.persist
   echo "In MyTrap"
}

main 1
[[ -f $HOME/.persist ]] && . $HOME/.persist || _ERROR_=0
[[ $_ERROR_ -eq 0 ]] || echo "failed"

Вывод вышеизложенного теперь:

In main: 1
In MyTrap
failed

Итак, возникает вопрос: Можно ли найти более простой подход к перехвату подоболочки set -e/ERR, невосприимчивый к ограничениям && и ||, чем описанный выше обходной путь?

Примечания. Этот вопрос касается:

  • Баш 4.2 и выше
  • Red Hat 7, CentOS 7 и родственные дистрибутивы (то есть не Debian и т. д.)
  • Никакое стороннее программное обеспечение не должно использоваться. Пакеты должны быть доступны, например, через пакеты OS-провайдера в iso-репозитории [CentOS-7-x86_64-DVD-1611.iso][2] (аналогично для RHEL 7, Fedora 7 и т. д.).

person Steve Amerige    schedule 22.01.2017    source источник
comment
Как насчет... не использовать set -e?   -  person gniourf_gniourf    schedule 23.01.2017
comment
Вы можете взглянуть на этот пост: stackoverflow.com/questions/41774696/   -  person codeforester    schedule 23.01.2017
comment
@gniourf_gniourf Пожалуйста, дайте мне знать, что, по вашему мнению, сработает вместо этого?   -  person Steve Amerige    schedule 23.01.2017
comment
@codeforester Я проверил вашу ссылку. Похоже, они просто добавляют больше встроенных тестов. Кроме того, в моем случае обратите внимание, что операции происходят в подоболочке. У меня уже все очень хорошо работает, за исключением обходного пути, которого я пытаюсь избежать. Кроме того, я не хочу тестировать каждую команду, которая может дать сбой. Вот почему я использую ловушку ERR с set -e. Это экономит мне огромное количество работы и делает код более удобным для сопровождения. Спасибо за комментарий.   -  person Steve Amerige    schedule 23.01.2017
comment
Более ремонтопригодность сомнительна. set -e поведение сильно различается между разными версиями оболочки и разными конфигурациями среды выполнения; ваш код может выглядеть чище, но это не означает, что он ведет себя последовательным и предсказуемым образом, что, я бы сказал, более важно для удобства сопровождения, чем лаконичность. Если вы еще не просмотрели его, см. BashFAQ #105.   -  person Charles Duffy    schedule 02.02.2017
comment
@SteveAmerige, ... итак, вкратце: (1) set -e - очень спорная функция в сообществе bash, и значительная часть седых волос считает, что от нее больше проблем, чем пользы; и (2) для людей из этого набора больше встроенных тестов на самом деле является предлагаемой альтернативой. Откровенно говоря, это мнение разделяют даже разработчики некоторых современных языков — см., например, подход Golang к явной обработке ошибок.   -  person Charles Duffy    schedule 02.02.2017
comment
Тем не менее, fvue.nl/wiki/Bash:_Error_handling содержит обзор необходимых обходных путей, которые вы может оказаться полезным -- хотя он не просматривает историю версий set -e и, таким образом, предостережения, которые вы можете найти в различных исторических выпусках оболочки. Если вам нужна совместимость между платформами, здесь еще будут драконы.   -  person Charles Duffy    schedule 02.02.2017


Ответы (1)


Этот код НЕ является решением. Это модифицированная версия, которую вы можете протестировать, чтобы получить некоторые идеи.

#!/bin/bash

main()
{
   local Arg="$1"
   (
      echo "In main: $Arg"
      $(exit 1)
      echo "Should not get here"
   )

   return 1
}

MyTrap()
{
   echo "In MyTrap"
   exit 1
}

set -o errtrace
set -o functrace
trap MyTrap ERR

main 1
[[ $? -eq 0 ]] || echo "failed"
echo
main 2 || echo "failed"

Он не будет делать то, что вы хотели бы (я предполагаю), потому что не хватает чего-то важного, что слишком сложно объяснить в посте, но есть несколько ключевых моментов.

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

  • Установите ловушку на верхнем уровне и НЕ используйте set -e
  • Используйте параметры оболочки, которые заставляют подоболочки и функции наследовать ловушки (показано в примере)
  • Создайте структуру, в которой вы будете различать «ожидаемые» ошибки (явно обрабатываемые как исключения, а не как коды возврата) и «неожиданные» исключения (ошибки), которые будут перехватываться.
  • Никогда не используйте логические операторы напрямую (за исключением тестовых конструкций или других простых операторов, которые вы считаете достаточно безопасными).
  • Выполняйте логические тесты после сбора исключений, а не как тесты кодов возврата.

Создать эту структуру очень сложно, но выполнимо (у меня она безупречно работает с десятками очень сложных скриптов). После того, как вы это сделаете, если вы решили слепо протестировать команду (без явной обработки исключений), в случае сбоя у вас есть возможность сбросить ваш скрипт в ловушку (exit) вместо того, чтобы продолжать выполнение с вашим скриптом в нестабильном состоянии.

Это, вероятно, влечет за собой небольшое снижение производительности (по крайней мере, так, как я это сделал, используя специальную функцию try, предшествующую каждому оператору, для которого используется обработка исключений), и, конечно, требует МНОГО дисциплины в кодировании, но в моем случае это заставляет меня более продуктивное создание сложных сценариев с разумной уверенностью, что местоположение любой ошибки (не то, чтобы у меня было что-то из этого...) будет намного проще определить.

person Fred    schedule 23.01.2017
comment
Да, вы правы, здесь слишком много всего для поста. На самом деле, я реализовал это в контексте среды сценариев Bash, которая учитывает все упомянутые вами вещи. В частности, я полностью реализовал механизм try/catch в Bash, который даже допускает вложенные try/catch с сохранением переменных. Но, все это слишком для здесь, я согласен! При попытке придумать минимальный тестовый пример многое теряется... и легко пропустить то, с чем я уже имел дело. Если хотите, мы можем обсудить это в чате. - person Steve Amerige; 23.01.2017
comment
Хорошо для чата .. Но я здесь слишком новичок, я даже не знаю, как общаться в чате, поэтому мне понадобится небольшая помощь. - person Fred; 23.01.2017
comment
Внизу каждой страницы есть ссылка на чат (в темно-серой области). Вы можете щелкнуть там, а затем отфильтровать с помощью bash, и мы можем договориться о конкретном чате. Это хороший подход, если вы заинтересованы в том, чтобы другие могли легко присоединиться к разговору. Кроме того, вы можете щелкнуть ссылку на мой профиль в нижней части исходного сообщения, затем изменить URL-адрес, чтобы он начинался с chat., а затем нажать кнопку start a new room with this user. Обсуждение по-прежнему открыто, но будет получать меньше трафика, чем если бы вы использовали общий чат. - person Steve Amerige; 23.01.2017
comment
Я создал общедоступный чат под названием «Обработка ошибок Bash». Есть ли способ пригласить пользователя или это нужно делать в комментариях? - person Fred; 23.01.2017
comment
Это требует некоторой координации. Вы можете установить звуковые уведомления в окне чата. Я нахожусь в США, Северная Каролина (UTC-05:00) и работаю с 04:00 до 15:00 в большинстве дней. Я оставлю сеанс чата открытым на день или два в созданной вами комнате. - person Steve Amerige; 24.01.2017