Используйте ловушку RETURN

Используйте ловушку RETURN

У меня есть функция, и я хотел бы использовать опцию pipefailвнутри. Но я не хочу просто set -o pipefail, потому что боюсь, что другая часть скрипта может не ожидать pipefailустановки. Конечно, я мог бы сделать это set +o pipefailпозже, но в случае pipefailустановки вне моей функции это также может привести к неожиданному поведению.

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

Есть ли более общий (и, может быть, канонический?) способ проверить установленные параметры bash?

решение1

В bash-4.4и выше вы можете использовать:

myfunction() {
  local -
  set -o someoption
  ...
}

Несколько заметок:

  • скопировано из ash. В ash, идея состоит в том, чтобы сделать $-(который хранит набор опций) локальным. Это работает в , ashпотому что в ash, localне влияет на значение или атрибуты переменной (в отличие от bashтого, где он изначально делает ее неустановленной). Тем не менее, это также работает с bashтем же способом, что и в ashкак особый случай, то есть это не приводит $-к неустановленности или возврату к поведению по умолчанию
  • он охватывает только параметры, заданные с помощью set -o option, а не те, которые заданы shopt -s option( bashбудучи единственной оболочкой с двумя наборами параметров!)
  • как и для локальных переменных, это динамическая область видимости, а не статическая. Значение $-будет восстановлено при возврате из функции, но новая настройка повлияет на другие функции или исходные скрипты, вызываемые вашей функцией. Если вам нужна статическая область видимости, вам нужно будет переключиться на ksh93 и использовать:

    function myfunction {
      set -o myoption
      ...
      other_function
    }
    

    На это other_functionне влияет, если только он объявлен с function other_function {синтаксисом (а не с синтаксисом Борна other_function()...).

  • Поскольку он не сбрасывает параметры, ваша функция все еще может быть затронута параметрами, установленными другим кодом. Чтобы избежать этого, вы бы хотели использовать zshвместо этого. zshэквивалент is local -, set -o localoptionsно вы также можете получить хорошо известныйэмуляциярежимлокально. Например:

    myfunction() {
      emulate -L zsh
      set -o someoption
      ...
    }
    

    начнется с разумного набора опций zsh-default (с некоторыми исключениями, см. документацию), поверх которого вы добавляете свою опцию. Это также динамическое определение области действия, как в ash/bash, но вы также можете вызывать другие функции с помощью:

    emulate zsh -c 'other_function...'
    

    для того, чтобы его other_functionможно было вызвать с установленным набором опций vanilla zsh.

    zsh также имеет ряд операторов, которые могут помочь вам избежать изменения опций в глобальном масштабе в первую очередь (например, (N)/ (D)glob-квалификаторы для nullglob/ dotglob, (#i)glob-оператор для подстановки без учета регистра, ${(s:x:)var}чтобы избежать смешивания с $IFS, ${~var}чтобызапросподстановка при расширении параметров (вам нужно noglobв других оболочках типа Bourneизбегать(!) это)...

решение2

Переменная $SHELLOPTSсодержит все заданные параметры, разделенные двоеточиями. Например, это может выглядеть так:

braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor

Для проверки заданной опции можно сделать следующее:

# check if pipefail is set, if not set it and remember this
if [[ ! "$SHELLOPTS" =~ "pipefail" ]]; then
    set -o pipefail;
    PIPEFAIL=0;
else
    PIPEFAIL=1;
fi

# do something here

# reset pipefail, if it was not set before
if [ "$PIPEFAIL" -eq 0 ]; then
    set +o pipefail;
fi

решение3

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

Вариант 1: подоболочка

Поместите функцию внутрь подоболочки (обратите внимание на круглые скобки, окружающие операторы функции, а не фигурные скобки):

function foo() (
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
)

Тогда вызывающая сторона может отключить pipefail и это не повлияет на нее:

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

или

Вариант 2: тест-сброс

При необходимости можно протестировать и сбросить параметр (обратите внимание на фигурные скобки вокруг операторов функций — подоболочки нет):

function foo() {
  shopt -q -o pipefail
  local resetpipefail=$?
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
  # only reset pipefail if needed
  [[ $resetpipefail -eq 1 ]] && set +o pipefail  
}

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

Снимаю шляпукуонглмдля ихshopt -qотвечать.

решение4

Используйте ловушку RETURN

Команды, указанные с помощью RETURNпрерывания, выполняются до возобновления выполнения после выполнения функции оболочки или сценария оболочки с помощью .или sourceвозвращаются.

[...]
Параметр -p[to shopt] позволяет отображать выходные данные в форме, которую можно повторно использовать в качестве входных данных.

https://www.gnu.org/software/bash/manual/bash.html

foo() {
  trap "$(shopt -p -o pipefail)" RETURN
  set -o pipefail
  echo inside foo
  shopt -o pipefail
  # ...
}

Тест

$ shopt -o pipefail
pipefail        off
$ foo
inside foo
pipefail        on
$ shopt -o pipefail
pipefail        off

Следующий шаблон функции обрабатывает общий случай, гарантируя, что оба параметра setи shoptвосстанавливаются до своих исходных значений при возврате функции:

foo() {
  trap "$(shopt -p -o; shopt -p)" RETURN # ①
  # ...
}

shopt -p -oможно упростить до стандартного эквивалента set +o.

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