Usa una trampa de RETORNO

Usa una trampa de RETORNO

Tengo una función y me gustaría usar la pipefailopción interna. Pero no quiero simplemente hacerlo set -o pipefail, porque temo que otra parte del guión no espere pipefailestar ambientada. Por supuesto que podría hacerlo set +o pipefaildespués, pero en caso de que pipefailse establezca fuera de mi función, esto también puede introducir un comportamiento inesperado.

Por supuesto, podría usar el código de salida false | truecomo medida, si pipefailestá configurado, pero esto parece un poco sucio.

¿Existe una forma más general (y tal vez canónica?) de verificar las opciones configuradas de bash?

Respuesta1

En bash-4.4y superiores, puedes usar:

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

Algunas notas:

  • eso está copiado de ash. En ash, la idea es hacer $-(que almacena el conjunto de opciones) local. Funciona ashporque en ash, localno afecta el valor ni los atributos de la variable (al contrario de bashdonde inicialmente la desarma). Aún así, también funciona de bashla misma manera que en ashun caso especial, es decir, no hace que $-se desarme ni vuelva a un comportamiento predeterminado.
  • solo cubre las opciones establecidas con set -o option, no las establecidas por shopt -s option( bash¡siendo el único shell con dos conjuntos de opciones!)
  • Al igual que con las variables locales, es un alcance dinámico, no estático. El valor de $-se restaurará al regresar de la función, pero la nueva configuración afectará a las otras funciones o scripts de origen llamados por su función. Si desea un alcance estático, deberá cambiar a ksh93 y usar:

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

    Con el other_functionno afectado siempre que se declare function other_function {también con la sintaxis (y no con la other_function()...sintaxis de Bourne).

  • Como no restablece las opciones, su función aún puede verse afectada por las opciones establecidas por otro código. Para evitar eso, querrás usarlo zshen su lugar. zshes el equivalente a local -es set -o localoptions, pero también puedes llegar a un conocidoemulaciónmodoen la zona. Por ejemplo:

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

    comenzaría con un conjunto de opciones zsh-default sensato (con algunas excepciones, consulte el documento) encima del cual agregaría su opción. Eso también es alcance dinámico como en ash/bash, pero también puedes llamar a otras funciones con:

    emulate zsh -c 'other_function...'
    

    para que eso other_functionse llame con un conjunto de opciones vanilla zsh.

    zsh también tiene una serie de operadores que pueden hacer que evite cambiar opciones globalmente en primer lugar (como calificadores (N)/ (D)glob para nullglob/ dotglob, (#i)operador glob para globalización que no distingue entre mayúsculas y minúsculas, ${(s:x:)var}para evitar mezclarse con $IFS, ${~var}parapedidoglobbing sobre la expansión de parámetros (necesita nogloben otros shells tipo Bourneevitar(!) él)...

Respuesta2

La $SHELLOPTSvariable contiene todas las opciones establecidas separadas por dos puntos. Como ejemplo, podría verse así:

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

Para verificar una opción determinada, se puede hacer lo siguiente:

# 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

Respuesta3

Si desea configurar una opción de shell dentro de una función, pero no desea afectar a la persona que llama, puede hacerlo de dos maneras diferentes:

Opción 1: subcapa

Coloque la función dentro de una subcapa (observe el paréntesis que rodea las declaraciones de la función en lugar de las llaves):

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

Entonces, la persona que llama puede tener desarmado el pipefail y no se ve afectado:

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

o

Opción 2: probar y restablecer

Puede probar y restablecer la opción según sea necesario (observe las llaves alrededor de las declaraciones de función, sin subcapa):

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

punta de sombrero paracuonglmpara ellosshopt -qrespuesta.

Respuesta4

Usa una trampa de RETORNO

Los comandos especificados con una RETURNcaptura se ejecutan antes de que se reanude la ejecución después de que se ejecute una función de shell o un script de shell con .o sourceregrese.

[...]
La -popción [comprar] hace que la salida se muestre en un formulario que puede reutilizarse 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
  # ...
}

Prueba

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

La siguiente plantilla de función maneja el caso general de garantizar que ambas opciones sety shoptse restablezcan a sus valores originales cuando regresa una función:

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

shopt -p -ose puede simplificar al equivalente estándar set +o.

información relacionada