Cómo guardar y restaurar todas las opciones de shell, incluido errexit

9

He leído muchas preguntas en varios sitios de intercambio de pila y foros de ayuda de Unix sobre cómo modificar las opciones de shell y luego restaurarlas. La más completa que encontré aquí es ¿Cómo "deshacer" un `set -x`?

La sabiduría recibida parece ser guardar el resultado de set +oo shopt -poy luego evalrestaurar la configuración anterior.

Sin embargo, en mis propias pruebas con bash 3.xy 4.x, la errexitopción no se guarda correctamente al realizar la sustitución de comandos.

Aquí hay un script de ejemplo para mostrar el problema:

set -o errexit
set -o nounset
echo "LOCAL SETTINGS:"
set +o
OLDOPTS=$(set +o)
echo
echo "SAVED SETTINGS:"
echo "$OLDOPTS"

Y salida (recorté algunas de las variables irrelevantes):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Esto parece extremadamente peligroso. La mayoría de los scripts que escribo dependen errexitde detener la ejecución si alguno de los comandos falla. Acabo de localizar un error en uno de mis scripts causado por esto, donde la función que se suponía que debía restaurarse errexital final terminó anulándolo, volviéndolo al valor predeterminado de apagado durante la duración del script.

Lo que me gustaría poder hacer es escribir funciones que puedan configurar las opciones según sea necesario y luego restaurar todas las opciones correctamente antes de salir. Pero parece que en la subshell invocada por la sustitución del comando errexitno se hereda.

No sé cómo ahorrar el resultado set +osin utilizar la sustitución de comandos o saltar a través de los aros FIFO. Puedo leer, $SHELLOPTSpero no se puede escribir ni escribir en evalformato.

Sé que una alternativa es usar una función de subshell , pero eso introduce muchos dolores de cabeza para poder registrar la salida y transferir múltiples variables.

Probablemente relacionado: /programming/29532904/bash-subshell-errexit-semantics (parece que hay una solución para bash 4.4 y versiones posteriores, pero prefiero tener una solución portátil)

Eliot
fuente
Esto no restaura las shoptopciones de bash set (como nullglob).
Isaac

Respuestas:

6

Lo que estás haciendo debería funcionar. Pero bash desactiva la errexitopción en las sustituciones de comandos, por lo que conserva todas las opciones excepto esta. Esto es específico de bash y específico de la errexitopción. Bash conserva errexitcuando se ejecuta en modo POSIX. Desde bash 4.4, bash tampoco se borra errexiten una sustitución de comando si shopt -s inherit_errexitestá vigente.

Dado que la opción está desactivada antes de que se ejecute ningún código dentro de la sustitución del comando, debe verificarlo afuera.

OLDOPTS=$(set +o)
case $- in
  *e*) OLDOPTS="$OLDOPTS; set -e";;
  *) OLDOPTS="$OLDOPTS; set +e";;
esac

Si no te gusta esta complejidad, usa zsh en su lugar.

setopt local_options
Gilles 'SO- deja de ser malvado'
fuente
3
Tenga en cuenta que bash4.4ahora tiene local -(à la ash) como equivalente a zsh's setopt localoptions(o lo que ksh88 hace por defecto)
Stéphane Chazelas
Vea también shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(los dos conjuntos de opciones son otra de bashlas idiosincrasias de).
Stéphane Chazelas
Más simple: OLDOPTS="$(set +o); set -$-".
Isaac
2

Después de probar lo anterior en alpine 3.6, ahora he tomado el siguiente enfoque mucho más simple:

OLDOPTS="$(set +o); set -${-//c}"
set -euf -o pipefail

... my stuff

# restore original options
set +vx; eval "${OLDOPTS}"

Según la documentación, "$ -" contiene la lista de opciones activas actualmente. Parece funcionar muy bien, ¿me estoy perdiendo algo?

Erich Eichinger
fuente
@ Isaac uso 'set + euf' porque $ - solo contiene la lista de opciones activas . es decir, al reiniciar, primero desactivo todo y luego reactivo solo la lista de opciones activas anteriormente (elijo 'euf' porque esas son las únicas opciones que generalmente modifico; para uso general, probablemente debería contener la lista completa de opciones posibles). Buen punto sobre 'set + vx', modifiqué el ejemplo anterior en consecuencia
Erich Eichinger el
eso puede funcionar para errexit pero no, por ejemplo, para verificar límites o globbing ( set -uf)
Erich Eichinger
@ Isaac me corrijo. No sé lo que hice mal. Muchas gracias por tu ayuda! También es bueno ver que te encontraste y resolviste el problema de la bandera -c - Ojalá hubiera visto tu comentario antes;)
Erich Eichinger
Actualicé la solución anterior basada en los comentarios de @Isaac
Erich Eichinger
1

errexit se propaga en sustituciones de proceso.

set -e

# Backup restore commands into an array
declare -a OPTS
readarray -t OPTS < <(shopt -po)

set +e

# Restore options
declare cmd
for cmd in "${OPTS[@]}"; do
    eval "$cmd"
done

Cheque:

$  shopt -po errexit
set -o errexit

Versión bash:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Sergej Alikov
fuente
1

La solución simple es agregar la errexitconfiguración a OLDOPTS:

OLDOPTS="$(set +o)"
[ "${BASH_VERSION:+x}" ] && shopt -qo errexit && OLDOPTS+=";set -e" || true

Hecho.

Isaac
fuente
Ver también [[ -o errexit ]](ksh / bash / zsh) y [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas el
El shoptes un comando válido para bash, no hay una razón significativa para evitarlo (solo es una cuestión de su preferencia personal de cómo se deben escribir las respuestas). No es un cambio significativo. @ StéphaneChazelas
Isaac