Use uma armadilha RETURN

Use uma armadilha RETURN

Tenho uma função e gostaria de usar a pipefailopção dentro dela. Mas não quero simplesmente fazê-lo set -o pipefail, porque temo que outra parte do roteiro não possa pipefailser definida. É claro que eu poderia fazer isso set +o pipefaildepois, mas caso pipefailtenha sido definido fora da minha função, isso também pode apresentar um comportamento inesperado.

É claro que eu poderia usar o código de saída false | truecomo medida, se pipefailestiver definido, mas isso parece um pouco sujo.

Existe uma maneira mais geral (e talvez canônica?) De verificar as opções definidas do bash?

Responder1

Acima bash-4.4e acima, você pode usar:

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

Algumas notas:

  • que foi copiado de ash. No ash, a ideia é tornar $-(que armazena o conjunto de opções) local. Funciona ashporque in ash, localnão afeta o valor nem os atributos da variável (ao contrário de bashonde inicialmente a desativa). Ainda assim, também funciona da bashmesma maneira que em ashum caso especial, ou seja, não causa $-a desativação ou a reversão para um comportamento padrão
  • cobre apenas as opções definidas com set -o option, não as definidas por shopt -s option( bashsendo o único shell com dois conjuntos de opções!)
  • como para variáveis ​​locais, é um escopo dinâmico, não estático. O valor de $-será restaurado ao retornar da função, mas a nova configuração afetará as outras funções ou scripts de origem chamados pela sua função. Se quiser um escopo estático, você precisará mudar para ksh93 e usar:

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

    Com o other_functionnão afetado, desde que seja declarado function other_function {também com a sintaxe (e não com a other_function()...sintaxe Bourne).

  • Como não redefine as opções, sua função ainda poderá ser afetada por opções definidas por outro código. Para evitar isso, você gostaria de usar zsh. zshé equivalente a local -is set -o localoptions, mas você também pode chegar a um conhecidoemulaçãomodolocalmente. Por exemplo:

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

    começaria com um conjunto sensato de opções zsh-default (com algumas exceções, consulte o documento) sobre o qual você adicionaria sua opção. Isso também é escopo dinâmico como no ash/bash, mas você também pode chamar outras funções com:

    emulate zsh -c 'other_function...'
    

    para que isso other_functionseja chamado com um conjunto de opções vanilla zsh.

    zsh também possui vários operadores que podem fazer com que você evite alterar opções globalmente em primeiro lugar (como (N)/ (D)qualificadores glob para nullglob/ dotglob, (#i)operador glob para globbing sem distinção entre maiúsculas e minúsculas, ${(s:x:)var}para evitar mistura com $IFS, ${~var}parasolicitarglobbing na expansão de parâmetros (você precisa noglobem outros shells do tipo Bourne paraevitar(!) isto)...

Responder2

A $SHELLOPTSvariável contém todas as opções definidas separadas por dois pontos. Por exemplo, pode parecer:

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

Para verificar uma determinada opção, pode-se fazer o seguinte:

# 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

Responder3

Se você deseja definir uma opção de shell dentro de uma função, mas não deseja afetar o chamador, faça isso de duas maneiras diferentes:

Opção 1: subshell

Coloque a função dentro de um subshell (observe os parênteses que cercam as instruções da função em vez das chaves):

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

Então, o chamador pode ter o pipefail desativado e não será afetado:

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

ou

Opção 2: testar e redefinir

Você pode testar e redefinir a opção conforme necessário (observe as chaves ao redor das instruções de função - sem subshell):

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

Gorjeta de chapéu paracuonglmpare elesshopt -qresponder.

Responder4

Use uma armadilha RETURN

Os comandos especificados com um RETURNtrap são executados antes da execução ser retomada após uma função shell ou um script shell executado com .ou sourceretornar.

[...]
A -popção [comprar] faz com que a saída seja exibida em um formato que pode ser reutilizado como entrada.

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
  # ...
}

Teste

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

O modelo de função a seguir trata do caso geral de garantir que ambas as opções sete shoptsejam restauradas aos seus valores originais quando uma função retorna:

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

shopt -p -opode ser simplificado para o equivalente padrão set +o.

informação relacionada