Come salvare e ripristinare tutte le opzioni della shell incluso errexit


9

Ho letto molte domande su vari siti di scambio di stack e unix help forum su come modificare le opzioni della shell e poi ripristinarle - quella più completa che ho trovato qui è su Come "annullare" un `set -x`?

La saggezza ricevuta sembra essere quella di salvare il risultato di set +oo shopt -poe evalsuccessivamente ripristinare le impostazioni precedenti.

Tuttavia, nei miei test con bash 3.xe 4.x, l' errexitopzione non viene salvata correttamente quando si esegue la sostituzione dei comandi.

Ecco uno script di esempio per mostrare il problema:

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

E output (ho eliminato alcune delle variabili irrilevanti):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Questo sembra estremamente pericoloso. La maggior parte degli script che scrivo dipendono dall'interruzione errexitdell'esecuzione se i comandi falliscono. Ho appena individuato un bug in uno dei miei script causato da questo, in cui la funzione che avrebbe dovuto ripristinare errexitalla fine si è conclusa sovrascrivendolo, riportandolo al valore predefinito di off per la durata dello script.

Quello che mi piacerebbe poter fare è scrivere funzioni che possono impostare le opzioni secondo necessità e quindi ripristinare tutte le opzioni correttamente prima di uscire. Ma sembra che nella subshell invocata dalla sostituzione del comando, errexitnon sia ereditata.

Sono in perdita per come salvare il risultato set +osenza usare la sostituzione dei comandi o saltare attraverso i cerchi FIFO. Sono in grado di leggere $SHELLOPTSma non è evalun formato scrivibile o non in grado.

So che un'alternativa è usare una funzione subshell , ma ciò introduce molti mal di testa per poter registrare l'output e restituire più variabili.

Probabilmente correlato: /programming/29532904/bash-subshell-errexit-semantics (sembra che ci sia una soluzione alternativa per bash 4.4 e versioni successive ma preferirei avere una soluzione portatile)


Questo non ripristina le shoptopzioni del set di bash (come nullglob).
Isaac,

Risposte:


6

Quello che stai facendo dovrebbe funzionare. Ma bash disattiva l' errexitopzione nelle sostituzioni di comandi, quindi conserva tutte le opzioni tranne questa. Questo è specifico per bash e specifico per l' errexitopzione. Bash mantiene errexitquando si esegue in modalità POSIX. Da bash 4.4, anche bash non cancella errexituna sostituzione di comando se shopt -s inherit_errexitè attiva.

Poiché l'opzione è disattivata prima dell'esecuzione di qualsiasi codice all'interno della sostituzione del comando, è necessario verificarlo all'esterno.

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

Se non ti piace questa complessità, usa invece zsh.

setopt local_options

3
Nota che bash4.4ora ha local -(à la ash) come equivalente a zsh's setopt localoptions(o cosa fa ksh88 di default)
Stéphane Chazelas,

Vedi anche shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(le due serie di opzioni sono un'altra delle bashidiosincrasie).
Stéphane Chazelas,

Più semplice: OLDOPTS="$(set +o); set -$-".
Isaac,

2

Dopo aver provato quanto sopra su alpine 3.6, ho adottato il seguente approccio molto più semplice:

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

... my stuff

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

come da documentazione, "$ -" contiene l'elenco delle opzioni attualmente attive. Sembra funzionare alla grande, mi sto perdendo qualcosa?


@Isaac Uso 'set + euf' perché $ - contiene solo l'elenco delle opzioni attive . vale a dire quando reimposto disattivo prima tutto e poi riattivo solo l'elenco delle opzioni precedentemente attive (scelgo 'euf' perché quelle sono le uniche opzioni che di solito modifico - per uso generale dovrebbe probabilmente contenere l'elenco completo delle opzioni possibili). Un buon punto su 'set + vx', ho modificato di conseguenza l'esempio sopra
Erich Eichinger,

che può funzionare per errexit ma non ad esempio per il controllo dei limiti o il globbing ( set -uf)
Erich Eichinger,

@Isaac Sono corretto. Non so cosa ho fatto di sbagliato. Grazie mille per il tuo aiuto! Bello anche vedere che hai incontrato e risolto il problema con la bandiera -c - Vorrei aver visto il tuo commento prima;)
Erich Eichinger,

Ho aggiornato la soluzione sopra basata sul feedback di @Isaac
Erich Eichinger,

1

errexit viene propagato in sostituzioni di processo.

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

Dai un'occhiata:

$  shopt -po errexit
set -o errexit

Versione Bash:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

1

La soluzione semplice è aggiungere l' errexitimpostazione a OLDOPTS:

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

Fatto.


Vedi anche [[ -o errexit ]](ksh / bash / zsh) e [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas,

Il shoptè un comando valido per bash, non v'è alcuna ragione significativa per evitarlo (I è solo una questione di preferenze personali di come le risposte dovrebbero essere scritte). Non è un cambiamento significativo. @ StéphaneChazelas
Isaac
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.