Comment enregistrer et restaurer toutes les options du shell, y compris errexit

9

J'ai lu de nombreuses questions sur divers sites d'échange de piles et forums d'aide Unix sur la façon de modifier les options du shell, puis de les restaurer - la plus complète que j'ai trouvée ici est Comment "annuler" un `set -x`?

La sagesse reçue semble être de sauvegarder le résultat de set +oou shopt -popuis evalde restaurer les paramètres précédents.

Cependant, dans mes propres tests avec bash 3.x et 4.x, l' errexitoption n'est pas enregistrée correctement lors de la substitution de commande.

Voici un exemple de script pour montrer le problème:

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

Et la sortie (j'ai supprimé certaines des variables non pertinentes):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Cela semble extrêmement dangereux. La plupart des scripts que j'écris dépendent errexitd'arrêter l'exécution si des commandes échouent. Je viens de localiser un bug dans l'un de mes scripts causé par cela, où la fonction qui était censée être restaurée errexità la fin a été remplacée, la redéfinissant par défaut sur off pendant la durée du script.

Ce que j'aimerais pouvoir faire, c'est écrire des fonctions qui peuvent définir des options selon les besoins, puis restaurer correctement toutes les options avant de quitter. Mais il semble que dans le sous-shell invoqué par la substitution de commande, il errexitne soit pas hérité.

Je ne sais pas comment économiser le résultat set +osans utiliser de substitution de commande ou sauter à travers des cercles FIFO. Je peux lire, $SHELLOPTSmais ce n'est pas accessible en écriture ou au evalformat.

Je sais qu'une alternative est d'utiliser une fonction de sous - shell , mais cela introduit beaucoup de maux de tête pour pouvoir enregistrer la sortie ainsi que transmettre plusieurs variables.

Probablement lié: /programming/29532904/bash-subshell-errexit-semantics (il semble qu'il existe une solution de contournement pour bash 4.4 et plus, mais je préfère avoir une solution portable)

Eliot
la source
Cela ne restaure pas les shoptoptions de l'ensemble bash (comme nullglob).
Isaac

Réponses:

6

Ce que vous faites devrait fonctionner. Mais bash désactive l' errexitoption dans les substitutions de commandes, il conserve donc toutes les options sauf celle-ci. Ceci est spécifique à bash et spécifique à l' errexitoption. Bash se conserve errexitlors de l'exécution en mode POSIX. Depuis bash 4.4, bash ne s'efface pas non plus errexitdans une substitution de commande si elle shopt -s inherit_errexitest en vigueur.

Étant donné que l'option est désactivée avant l'exécution de tout code à l'intérieur de la substitution de commande, vous devez la vérifier à l'extérieur.

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

Si vous n'aimez pas cette complexité, utilisez plutôt zsh.

setopt local_options
Gilles 'SO- arrête d'être méchant'
la source
3
Notez que bash4.4a maintenant local -(à la ash) comme équivalent à zshl » setopt localoptions(ou ce que ksh88 fait par défaut)
Stéphane Chazelas
Voir aussi shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(les deux ensembles d'options sont une autre bashidiosyncrasie de).
Stéphane Chazelas
Simple: OLDOPTS="$(set +o); set -$-".
Isaac
2

Après avoir essayé l'ancien de ce qui précède sur alpine 3.6, j'ai maintenant adopté l'approche beaucoup plus simple suivante:

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

... my stuff

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

selon la documentation, "$ -" contient la liste des options actuellement actives. Semble fonctionner très bien, est-ce que je manque quelque chose?

Erich Eichinger
la source
@Isaac J'utilise 'set + euf' car $ - ne contient que la liste des options actives . c'est-à-dire que lors de la réinitialisation, je désactive tout d'abord, puis réactive uniquement la liste des options précédemment actives (je choisis 'euf' car ce sont les seules options que je modifie habituellement - pour une utilisation générale, il devrait probablement contenir la liste complète des options possibles). Bon point sur 'set + vx', j'ai modifié l'exemple ci-dessus en conséquence
Erich Eichinger
cela peut fonctionner pour l'errexit mais pas pour la vérification des limites ou la globalisation ( set -uf)
Erich Eichinger
@Isaac je me corrige. Je ne sais pas ce que j'ai fait de mal. Merci beaucoup pour votre aide! Aussi bon de voir que vous avez rencontré et résolu le problème du drapeau -c - j'aurais aimé avoir vu votre commentaire plus tôt;)
Erich Eichinger
J'ai mis à jour la solution ci-dessus en fonction des commentaires de @Isaac
Erich Eichinger
1

errexit se propage en substitutions de processus.

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

Vérifier:

$  shopt -po errexit
set -o errexit

Version basique:

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

La solution simple consiste à ajouter le errexitparamètre à OLDOPTS:

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

Terminé.

Isaac
la source
Voir aussi [[ -o errexit ]](ksh / bash / zsh) et [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas
Il shopts'agit d'une commande valide pour bash, il n'y a aucune raison valable de l'éviter (je ne fais que déterminer votre façon de rédiger les réponses). Pas un changement significatif. @ StéphaneChazelas
Isaac