Comment restaurer la valeur des options du shell comme `set -x`?

47

Je veux set -xau début de mon script et le "défaire" (revenir à l'état avant que je le définisse) après au lieu de régler aveuglément +x. Est-ce possible?

PS: J'ai déjà vérifié ici ; cela n'a pas semblé répondre à ma question pour autant que je sache.

Roney Michael
la source

Réponses:

59

Abstrait

Pour inverser un, set -xexécutez simplement un set +x. La plupart du temps, l'inverse d'une chaîne set -strest la même chaîne avec +: set +str.

En général, pour restaurer toutes errexitles options du shell (modifiées ci-dessous à l' aide de la setcommande) (lisez ci-dessous à propos de bash shopt), procédez comme suit (lisez également ci-dessous les options de bash ):

oldstate="$(set +o); set -$-"                # POSIXly store all set options.
.
.
set -vx; eval "$oldstate"         # restore all options stored.

Description plus longue

frapper

Cette commande:

shopt -po xtrace

générera une chaîne exécutable qui reflète l'état de l'option. L' pindicateur signifie imprimer et il oindique que nous posons des questions sur les options définies par la setcommande (par opposition aux options définies par la shoptcommande). Vous pouvez affecter cette chaîne à une variable et l'exécuter à la fin de votre script pour restaurer l'état initial.

# store state of xtrace option.
tracestate="$(shopt -po xtrace)"

# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"

# restore the value of xtrace to its original value.
eval "$tracestate"

Cette solution fonctionne pour plusieurs options simultanément:

oldstate="$(shopt -po xtrace noglob errexit)"

# change options as needed
set -x
set +x
set -f
set -e
set -x

# restore to recorded state:
set +vx; eval "$oldstate"

L'ajout set +vxévite l'impression d'une longue liste d'options.


Et, si vous ne listez aucun nom d'option,

oldstate="$(shopt -po)"

il vous donne les valeurs de toutes les options. Et, si vous laissez le odrapeau de côté, vous pouvez faire la même chose avec des shoptoptions:

# store state of dotglob option.
dglobstate="$(shopt -p dotglob)"

# store state of all options.
oldstate="$(shopt -p)"

Si vous devez vérifier si une setoption est définie, le moyen le plus idiomatique (Bash) de le faire est:

[[ -o xtrace ]]

qui est meilleur que les deux autres tests similaires:

  1. [[ $- =~ x ]]
  2. [[ $- == *x* ]]

Avec n'importe lequel des tests, cela fonctionne:

# record the state of the xtrace option in ts (tracestate):
[ -o xtrace ] && ts='set -x' || ts='set +x'

# change xtrace as needed
echo "some commands with xtrace as externally selected"
set -x
echo "some commands with xtrace set"

# set the xtrace option back to what it was.
eval "$ts"

Voici comment tester l'état d'une shoptoption:

if shopt -q dotglob
then
        # dotglob is set, so “echo .* *” would list the dot files twice.
        echo *
else
        # dotglob is not set.  Warning: the below will list “.” and “..”.
        echo .* *
fi

POSIX

Une solution simple et compatible POSIX pour stocker toutes les setoptions est la suivante:

set +o

qui est décrit dans la norme POSIX comme:

+ o

    Ecrivez les paramètres d’option actuels dans la sortie standard dans un format adapté pour être réintroduit dans le shell en tant que commandes réalisant les mêmes paramètres d’option.

Alors, simplement:

oldstate=$(set +o)

conservera les valeurs pour toutes les options définies à l'aide de la setcommande.

Encore une fois, restaurer les options à leurs valeurs d'origine consiste à exécuter la variable:

set +vx; eval "$oldstate"

Ceci est exactement équivalent à l'utilisation de Bash shopt -po. Notez que cela ne couvrira pas toutes les options possibles de Bash , car certaines sont définies par shopt.

cas spécial bash

Il y a beaucoup d'autres options de shell listées avec shoptdans bash:

$ shopt
autocd          off
cdable_vars     off
cdspell         off
checkhash       off
checkjobs       off
checkwinsize    on
cmdhist         on
compat31        off
compat32        off
compat40        off
compat41        off
compat42        off
compat43        off
complete_fullquote  on
direxpand       off
dirspell        off
dotglob         off
execfail        off
expand_aliases  on
extdebug        off
extglob         off
extquote        on
failglob        off
force_fignore   on
globasciiranges off
globstar        on
gnu_errfmt      off
histappend      on
histreedit      off
histverify      on
hostcomplete    on
huponexit       off
inherit_errexit off
interactive_comments    on
lastpipe        on
lithist         off
login_shell     off
mailwarn        off
no_empty_cmd_completion off
nocaseglob      off
nocasematch     off
nullglob        off
progcomp        on
promptvars      on
restricted_shell    off
shift_verbose   off
sourcepath      on
xpg_echo        off

Ceux-ci pourraient être ajoutés à la variable définie ci-dessus et restaurés de la même manière:

$ oldstate="$oldstate;$(shopt -p)"
.
.                                   # change options as needed.
.
$ eval "$oldstate" 

Il est possible de faire (le $-est ajouté pour assurer sa errexitconservation):

oldstate="$(shopt -po; shopt -p); set -$-"

set +vx; eval "$oldstate"             # use to restore all options.

Remarque : chaque shell a une manière légèrement différente de construire la liste des options définies ou non définies (pour ne pas mentionner différentes options définies), de sorte que les chaînes ne sont pas portables entre les shells, mais sont valides pour le même shell.

cas spécial zsh

zshfonctionne également correctement (suivant POSIX) depuis la version 5.3. Dans les versions précédentes , il a suivi que partiellement Posix set +oen ce qu'elle imprime des options dans un format qui a été adapté pour Reinput à la coquille sous forme de commandes, mais seulement pour fixer les options (il n'a pas imprimer un-ensemble des options).

cas particulier de mksh

Le mksh (et par conséquent lksh) n'est pas encore capable (MIRBSD KSH R54 2016/11/11) de le faire. Le manuel mksh contient ceci:

Dans une version ultérieure, set + o se comportera de manière conforme à POSIX et imprimera des commandes pour restaurer les options en cours.

set -e cas particulier

Dans bash, la valeur de set -e( errexit) est réinitialisée à l'intérieur des sous-coquilles, ce qui rend difficile la capture de sa valeur à l' set +ointérieur d'un sous-shell $ (…).

Pour contourner le problème, utilisez:

oldstate="$(set +o); set -$-"
sorontar
la source
21

Avec le shell Almquist et ses dérivés ( dash, NetBSD / FreeBSD shau moins) et bash4.4 ou supérieur, vous pouvez créer des options locales à une fonction avec local -(indiquez la $-variable locale si vous le souhaitez):

$ bash-4.4 -c 'f() { local -; set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

Cela ne s'applique pas aux fichiers sources , mais vous pouvez redéfinir sourceen source() { . "$@"; }fonction de cela.

Avec ksh88, les modifications d’option sont locales à la fonction par défaut. Avec ksh93, ce n'est que le cas pour les fonctions définies avec la function f { ...; }syntaxe (et la portée est statique par rapport à la portée dynamique utilisée dans d'autres shells, y compris ksh88):

$ ksh93 -c 'function f { set -x; echo test; }; f; echo no trace'
+ echo test
test
no trace

En zsh, cela se fait avec l' localoptionsoption:

$ zsh -c 'f() { set -o localoptions; set -x; echo test; }; f; echo no trace'
+f:0> echo test
test
no trace

POSIXly, vous pouvez faire:

case $- in
  (*x*) restore=;;
  (*) restore='set +x'; set -x
esac
echo test
{ eval "$restore";} 2> /dev/null
echo no trace

Cependant, certains shells généreront un + 2> /dev/nulllors de la restauration (et vous verrez bien sûr la trace de cette caseconstruction si elle set -xétait déjà activée). Cette approche n'est pas non plus ré-entrante (comme si vous le faites dans une fonction qui s'appelle elle-même ou une autre fonction qui utilise le même truc).

Voir https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (portée locale pour les variables et options pour les shells POSIX) pour savoir comment mettre en œuvre une pile qui fonctionne autour de cela.

Avec n'importe quel shell, vous pouvez utiliser des sous-shell pour limiter la portée des options

$ sh -c 'f() (set -x; echo test); f; echo no trace'
+ echo test
test
no trace

Cependant, cela limite la portée de tout (variables, fonctions, alias, redirections, répertoire de travail en cours ...), pas seulement les options.

Stéphane Chazelas
la source
bash 4.4 est sorti il ​​y a 4 jours (16-septembre-2016), alors vous devrez probablement le recompiler vous-même, mais pourquoi pas
edi9999
Il me semble que oldstate=$(set +o)c'est un moyen plus simple (et POSIX) de stocker toutes les options.
sorontar
@sorontar, bon point cependant, cela ne fonctionne pas pour les implémentations de shell telles que pdksh/ mksh(et autres dérivés de pdksh) ou zshpour les set +osorties uniquement les écarts par rapport aux paramètres par défaut. Cela fonctionnerait pour bash / dash / yash mais ne serait pas portable.
Stéphane Chazelas
13

Vous pouvez lire la $-variable au début pour voir si elle -xest définie ou non, puis l'enregistrer dans une variable, par exemple

if [[ $- == *x* ]]; then
  was_x_set=1
else
  was_x_set=0
fi

Du manuel de Bash :

($ -, un trait d'union.) Développe les indicateurs d'option actuels spécifiés lors de l'appel, par la commande intégrée set ou ceux définis par le shell lui-même (telle que l'option -i).

Dawid Grabowski
la source
7
[[ $SHELLOPTS =~ xtrace ]] && wasset=1
set -x
echo rest of your script
[[ $wasset -eq 0 ]] && set +x

Dans bash, $ SHELLOPTS est défini avec les drapeaux activés. Vérifiez-le avant d'allumer xtrace, et réinitialisez xtrace uniquement s'il était éteint auparavant.

Jeff Schaller
la source
5

Juste pour énoncer l'évidence, si set -xdoit être en vigueur pendant la durée du script et qu'il ne s'agit que d'une mesure de test temporaire (ne faisant pas partie intégrante de la sortie), vous pouvez soit appeler le script avec l' -xoption, par exemple,

$ bash -x path_to_script.sh

... ou modifiez temporairement le script (première ligne) pour activer la sortie de débogage en ajoutant l' -xoption suivante:

#!/bin/bash -x
...rest of script...

Je réalise que c'est probablement un trait trop large pour ce que vous voulez, mais c'est le moyen le plus simple et le plus rapide d'activer / désactiver, sans trop compliquer le script avec des éléments temporaires que vous voudrez probablement supprimer de toute façon (d'après mon expérience).

Michael
la source
3

Ceci fournit des fonctions pour enregistrer et restaurer les indicateurs visibles via le $-paramètre spécial POSIX . Nous utilisons l' localextension pour les variables locales. Dans un script portable conforme à POSIX, les variables globales seraient utilisées (pas de localmot clé):

save_opts()
{
  echo $-
}

restore_opts()
{
  local saved=$1
  local on
  local off=$-

  while [ ${#saved} -gt 0 ] ; do
    local rest=${saved#?}
    local first=${saved%$rest}

    if echo $off | grep -q $first ; then
      off=$(echo $off | tr -d $first)
    fi

    on="$on$first"
    saved=$rest
  done

  set ${on+"-$on"} ${off+"+$off"}
}

Ceci est utilisé de la même manière que la façon dont les indicateurs d'interruption sont sauvegardés et restaurés dans le noyau Linux:

Shell:                                Kernel:

flags=$(save_opts)                    long flags;
                                      save_flags (flags);

set -x  # or any other                local_irq_disable(); /* disable irqs on this core */

# ... -x enabled ...                  /* ... interrupts disabled ... */

restore_opts $flags                   restore_flags(flags);

# ... x restored ...                  /* ... interrupts restored ... */

Cela ne fonctionnera pour aucune des options étendues qui ne sont pas couvertes par la $-variable.

Je viens de remarquer que POSIX a ce que je cherchais: l' +oargument de setn'est pas une option, mais une commande qui vide un tas de commandes qui, si evalelles sont restaurées, restaurent les options. Alors:

flags=$(set +o)

set -x

# ...

eval "$flags"

C'est ça.

Un petit problème est que si l' -xoption est activée avant cela eval, une multitude de set -ocommandes est vue. Pour éliminer cela, nous pouvons procéder comme suit:

set +x             # turn off trace not to see the flurry of set -o.
eval "$flags"      # restore flags
Kaz
la source
1

Vous pouvez utiliser un sous-shell.

(
   set 
   do stuff
)
Other stuff, that set does not apply to
ctrl-alt-delor
la source
0

Semblable à ce que @Jeff Shaller a dit mais réglé pour NE PAS faire écho à cette partie au milieu (ce dont j'avais besoin):

OPTS=$SHELLOPTS ; set +x
echo 'message' # or whatever
[[ $OPTS =~ xtrace ]] && set -x
Lee Meador
la source