Come ripristinare il valore delle opzioni della shell come `set -x`?


47

Voglio set -xall'inizio del mio copione e "annullarlo" (tornare allo stato prima di impostarlo) in seguito anziché impostarlo alla cieca +x. È possibile?

PS: ho già controllato qui ; quello non sembrava rispondere alla mia domanda per quanto potevo dire.

Risposte:


59

Astratto

Per invertire a set -xbasta eseguire a set +x. La maggior parte del tempo, il contrario di una stringa set -strè la stessa stringa con un +: set +str.

In generale, per ripristinare tutte errexitle opzioni di shell (leggi di seguito su bash ) (modificate con il setcomando) potresti fare (leggi anche di seguito sulle shoptopzioni di bash ):

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

Descrizione più lunga

bash

Questo comando:

shopt -po xtrace

genererà una stringa eseguibile che riflette lo stato dell'opzione. La pbandiera significa stampa e la obandiera specifica che stiamo chiedendo delle opzioni impostate dal setcomando (al contrario delle opzioni impostate dal shoptcomando). È possibile assegnare questa stringa a una variabile ed eseguire la variabile alla fine dello script per ripristinare lo stato iniziale.

# 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"

Questa soluzione funziona contemporaneamente per più opzioni:

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'aggiunta set +vxevita la stampa di un lungo elenco di opzioni.


E, se non elenchi alcun nome di opzione,

oldstate="$(shopt -po)"

ti dà i valori di tutte le opzioni. E, se lasci la obandiera, puoi fare le stesse cose con le shoptopzioni:

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

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

Se devi verificare se setun'opzione è impostata, il modo più idiomatico (Bash) per farlo è:

[[ -o xtrace ]]

che è meglio degli altri due test simili:

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

Con uno qualsiasi dei test, questo funziona:

# 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"

Ecco come testare lo stato di shoptun'opzione:

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

Una soluzione semplice, conforme a POSIX per memorizzare tutte le setopzioni è:

set +o

che è descritto nello standard POSIX come:

+ o

    Scrivi le impostazioni correnti delle opzioni nell'output standard in un formato adatto per il ripristino nella shell come comandi che ottengono le stesse impostazioni delle opzioni.

Quindi, semplicemente:

oldstate=$(set +o)

conserverà i valori per tutte le opzioni impostate usando il setcomando.

Ancora una volta, ripristinare le opzioni ai loro valori originali è una questione di esecuzione della variabile:

set +vx; eval "$oldstate"

Questo è esattamente equivalente all'utilizzo di Bash shopt -po. Nota che non coprirà tutte le possibili opzioni di Bash , poiché alcune di queste sono impostate da shopt.

bash caso speciale

Ci sono molte altre opzioni di shell elencate con shoptin 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

Questi potrebbero essere aggiunti alla variabile impostata sopra e ripristinati allo stesso modo:

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

È possibile fare ( $-è allegato per garantire che errexitsia preservato):

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

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

Nota : ogni shell ha un modo leggermente diverso di creare un elenco di opzioni impostate o non impostate (per non parlare delle diverse opzioni definite), quindi le stringhe non sono portatili tra le shell, ma sono valide per la stessa shell.

caso speciale zsh

zshfunziona anche correttamente (seguendo POSIX) dalla versione 5.3. Nelle versioni precedenti seguiva POSIX solo parzialmente set +oin quanto stampava le opzioni in un formato che era adatto per essere reintegrato nella shell come comandi, ma solo per le opzioni impostate (non stampava opzioni non impostate ).

caso speciale mksh

Il mksh (e di conseguenza lksh) non è ancora (MIRBSD KSH R54 2016/11/11) in grado di farlo. Il manuale di mksh contiene questo:

In una versione futura, set + o si comporterà con POSIX e stampa i comandi per ripristinare invece le opzioni correnti.

set -e caso speciale

In bash, il valore di set -e( errexit) viene resettato all'interno di sotto-shell, il che rende difficile catturarne il valore con set +oall'interno di una sotto-shell $ (...).

Per ovviare al problema, utilizzare:

oldstate="$(set +o); set -$-"

21

Con la shell e i derivati ​​Almquist ( almeno dashNetBSD / FreeBSD sh) e bash4.4 o versioni successive, puoi rendere le opzioni locali in una funzione con local -(rendi la $-variabile locale se vuoi):

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

Ciò non si applica ai file di origine , ma è possibile ridefinire sourcecome source() { . "$@"; }aggirare quello.

Con ksh88, le modifiche alle opzioni sono locali per la funzione per impostazione predefinita. Con ksh93, questo è solo il caso per le funzioni definite con la function f { ...; }sintassi (e lo scoping è statico rispetto allo scoping dinamico usato in altre shell tra cui ksh88):

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

In zsh, questo è fatto con l' localoptionsopzione:

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

POSIXly, puoi fare:

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

Tuttavia alcune shell produrranno a + 2> /dev/nulldopo il ripristino (e ovviamente vedrai la traccia di quel casecostrutto se set -xera già abilitato). Anche questo approccio non rientra (come se lo facessi in una funzione che chiama se stessa o un'altra funzione che utilizza lo stesso trucco).

Vedi https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (ambito locale per variabili e opzioni per shell POSIX) per come implementare uno stack che funziona attorno a quello.

Con qualsiasi shell, è possibile utilizzare subshells per limitare l'ambito delle opzioni

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

Tuttavia, ciò limita l'ambito di tutto (variabili, funzioni, alias, reindirizzamenti, directory di lavoro corrente ...), non solo opzioni.


bash 4.4 è uscito 4 giorni fa (16-settembre-2016), quindi probabilmente dovrai ricompilarlo da solo, ma perché no
edi9999

Mi sembra che oldstate=$(set +o)sia un modo più semplice (e POSIX) per memorizzare tutte le opzioni.
sorontar

@sorontar, buon punto però che non funziona per le implementazioni della shell come pdksh/ mksh(e altri derivati ​​pdksh) o zshdove set +ogenera solo le deviazioni dalle impostazioni predefinite. Funzionerebbe con bash / dash / yash ma non sarebbe portatile.
Stéphane Chazelas,

13

Puoi leggere la $-variabile all'inizio per vedere se -xè impostata o meno e quindi salvarla in una variabile, ad es

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

Dal manuale di Bash :

($ -, un trattino.) Si espande ai flag delle opzioni correnti come specificato al momento dell'invocazione, tramite il comando set builtin, o quelli impostati dalla shell stessa (come l'opzione -i).


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

In bash, $ SHELLOPTS è impostato con i flag che sono attivi. Controllalo prima di attivare xtrace e ripristina xtrace solo se prima era spento.


5

Giusto per indicare l'ovvio, se set -xdeve essere in vigore per la durata dello script, e questa è solo una misura di prova temporanea (per non essere permanentemente parte dell'output), quindi invocare lo script con l' -xopzione, ad es.

$ bash -x path_to_script.sh

... oppure, modifica temporaneamente lo script (prima riga) per abilitare l'output di debug aggiungendo l' -xopzione:

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

Mi rendo conto che probabilmente questo è un colpo troppo ampio per quello che vuoi, ma è il modo più semplice e veloce per abilitare / disabilitare, senza complicare eccessivamente lo script con elementi temporanei che probabilmente vorrai rimuovere comunque (nella mia esperienza).


3

Ciò fornisce funzioni per salvare e ripristinare i flag che sono visibili attraverso il $-parametro speciale POSIX . Usiamo l' localestensione per le variabili locali. In uno script portatile conforme a POSIX, verrebbero utilizzate le variabili globali (nessuna localparola chiave):

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"}
}

Questo è usato in modo simile al modo in cui i flag di interrupt vengono salvati e ripristinati nel kernel 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 ... */

Questo non funzionerà per nessuna delle opzioni estese che non sono coperte nella $-variabile.

Ho appena notato che POSIX ha quello che stavo cercando: l' +oargomento setnon è un'opzione, ma un comando che scarica un gruppo di comandi che, se eval-ed ripristinerà le opzioni. Così:

flags=$(set +o)

set -x

# ...

eval "$flags"

Questo è tutto.

Un piccolo problema è che se l' -xopzione è attivata prima di questo eval, set -osi vede una brutta raffica di comandi. Per eliminare questo, possiamo fare quanto segue:

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

1

Puoi usare una sotto-shell.

(
   set 
   do stuff
)
Other stuff, that set does not apply to

0

Simile a quello che ha detto @Jeff Shaller ma impostato su NON riecheggia quella parte nel mezzo (che è quello di cui avevo bisogno):

OPTS=$SHELLOPTS ; set +x
echo 'message' # or whatever
[[ $OPTS =~ xtrace ]] && set -x
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.