Используя set -e/set + e в bash с функциями

Я использовал простую преамбулу bash, как это, в моих скриптах:

#!/bin/bash
set -e

В сочетании с модулярностью/использованием функций это укусило меня сегодня.

Итак, скажем, у меня есть функция где-то вроде

foo() {
  #edit: some error happens that make me want to exit the function and signal that to the caller 
  return 2
}

В идеале я хотел бы иметь возможность использовать несколько небольших файлов, включать их функции в другие файлы, а затем вызывать эти функции, например

set +e
foo
rc=$?
set -e

. Это работает ровно на два уровня подпрограмм. Но если foo также вызывает такие подпрограммы, последний параметр перед возвратом будет set -e, который сделает вывод script на возврате - я не могу переопределить это в вызывающей функции. Итак, мне нужно было

foo() {
  #calling bar() in a shielded way like above
  #..      

  set +e
  return 2
}

Который я нахожу очень противоречивым (а также не тем, что хочу), что, если в некоторых контекстах я хотел бы использовать функцию без защиты от сбоев, а в других контекстах я хочу обработать очистку?) Какой лучший способ справиться с этим? Btw. Я делаю это на OSX, я не тестировал, отличается ли это поведение от Linux.

Ответ 1

Функции оболочки действительно не имеют "возвращаемых значений", просто выходят из кода.

Вы можете добавить && : к вызывающему, это делает команду "проверено" и не выйдет из нее:

foo() {
    echo 'x'
    return 42
}

out=$(foo && :)
echo $out

: - это "нулевая команда" (т.е. ничего не делает). В этом случае он даже не выполняется, так как он запускается только в том случае, если foo возвращает 0 (а это не так).

Выводится:

x

Это, возможно, немного уродливо, но опять же, все сценарии оболочки, возможно, немного уродливы; -)

Цитата sh(1) из FreeBSD, которая объясняет это лучше, чем bash man-страница:

 -e errexit
         Exit immediately if any untested command fails in non-interactive
         mode.  The exit status of a command is considered to be explicitly
         tested if the command is part of the list used to control an if,
         elif, while, or until; if the command is the left hand operand of
         an "&&" or "||" operator; or if the command is a pipeline preceded
         by the ! operator.  If a shell function is executed and its exit
         status is explicitly tested, all commands of the function are con‐
         sidered to be tested as well.