¿Cómo restaurar el valor de las opciones de shell como `set -x`?

47

Quiero al set -xprincipio de mi script y "deshacerlo" (volver al estado antes de configurarlo) después en lugar de configurarlo a ciegas +x. es posible?

PD: ya he comprobado aquí ; eso no pareció responder a mi pregunta por lo que pude ver.

Roney Michael
fuente

Respuestas:

59

Resumen

Para invertir un set -xsolo ejecute a set +x. La mayoría de las veces, el reverso de una cadena set -stres la misma cadena con un +: set +str.

En general, para restaurar todas las errexitopciones de shell (lea a continuación sobre bash ) (cambiado con el setcomando) que podría hacer (también lea a continuación sobre las shoptopciones de bash ):

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

Descripción más larga

golpetazo

Este comando:

shopt -po xtrace

generará una cadena ejecutable que refleje el estado de la opción. La pbandera significa imprimir, y la obandera especifica que estamos preguntando acerca de las opciones establecidas por el setcomando (a diferencia de las opciones establecidas por el shoptcomando). Puede asignar esta cadena a una variable y ejecutar la variable al final de su script para restaurar el estado inicial.

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

Esta solución funciona para múltiples opciones simultáneamente:

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"

Agregar set +vxevita la impresión de una larga lista de opciones.


Y, si no incluye nombres de opciones,

oldstate="$(shopt -po)"

te da los valores de todas las opciones. Y, si deja de lado la obandera, puede hacer lo mismo con las shoptopciones:

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

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

Si necesita probar si una setopción está configurada, la forma más idiomática (Bash) de hacerlo es:

[[ -o xtrace ]]

que es mejor que las otras dos pruebas similares:

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

Con cualquiera de las pruebas, esto funciona:

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

Aquí le mostramos cómo probar el estado de una shoptopción:

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 solución simple y compatible con POSIX para almacenar todas las setopciones es:

set +o

que se describe en el estándar POSIX como:

+ o

    Escriba la configuración de opciones actual en la salida estándar en un formato que sea adecuado para reiniciar en el shell como comandos que logran la misma configuración de opciones.

Tan simplemente:

oldstate=$(set +o)

conservará los valores para todas las opciones establecidas con el setcomando

Nuevamente, restaurar las opciones a sus valores originales es cuestión de ejecutar la variable:

set +vx; eval "$oldstate"

Esto es exactamente equivalente a usar Bash's shopt -po. Tenga en cuenta que no cubrirá todas las opciones posibles de Bash , ya que algunas de ellas están establecidas por shopt.

caso especial de bash

Hay muchas otras opciones de shell enumeradas shopten 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

Esos podrían agregarse a la variable establecida anteriormente y restaurarse de la misma manera:

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

Es posible hacer ( $-se adjunta para garantizar que errexitse conserva):

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

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

Nota : cada shell tiene una forma ligeramente diferente de crear la lista de opciones que están configuradas o no (sin mencionar las diferentes opciones que están definidas), por lo que las cadenas no son portables entre shells, pero son válidas para el mismo shell.

caso especial zsh

zshTambién funciona correctamente (después de POSIX) desde la versión 5.3. En versiones anteriores, seguía a POSIX solo parcialmente, ya set +oque imprimía las opciones en un formato que era adecuado para reintroducir en el shell como comandos, pero solo para las opciones establecidas (no imprimía las opciones no establecidas ).

caso especial mksh

El mksh (y, por consiguiente, lksh) todavía no puede (MIRBSD KSH R54 2016/11/11) hacer esto. El manual de mksh contiene esto:

En una versión futura, set + o se comportará conforme a POSIX e imprimirá comandos para restaurar las opciones actuales.

set -e caso especial

En bash, el valor de set -e( errexit) se restablece dentro de las subcapas, lo que hace que sea difícil capturar su valor set +odentro de una subcapa $ (...).

Como solución alternativa, use:

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

Con el shell de Almquist y sus derivados ( dash, NetBSD / FreeBSD shal menos) y bash4.4 o superior, puede hacer que las opciones sean locales para una función con local -(haga la $-variable local si lo desea):

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

Esto no se aplica a la procedencia archivos, pero se puede redefinir sourcecomo source() { . "$@"; }para evitar esto.

Con ksh88, los cambios de opción son locales a la función de forma predeterminada. Con ksh93, ese es solo el caso de las funciones definidas con la function f { ...; }sintaxis (y el alcance es estático en comparación con el alcance dinámico utilizado en otros shells, incluido ksh88):

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

En zsh, eso se hace con la localoptionsopción:

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

POSIXY, puedes hacer:

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

Sin embargo, algunos shells generarán un + 2> /dev/nullsobre la restauración (y verá el rastro de esa caseconstrucción, por supuesto, si set -xya estaba habilitado). Ese enfoque tampoco es reentrante (como si lo haces en una función que se llama a sí misma u otra función que usa el mismo truco).

Consulte https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (ámbito local para variables y opciones para shells POSIX) para saber cómo implementar una pila que funcione alrededor de eso.

Con cualquier shell, puede usar subshells para limitar el alcance de las opciones

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

Sin embargo, eso limita el alcance de todo (variables, funciones, alias, redirecciones, directorio de trabajo actual ...), no solo las opciones.

Stéphane Chazelas
fuente
bash 4.4 salió hace 4 días (16 de septiembre de 2016), por lo que probablemente tendrá que volver a compilarlo usted mismo, pero ¿por qué no?
edi9999
Me parece que oldstate=$(set +o)es una forma más simple (y POSIX) de almacenar todas las opciones.
sorontar
@sorontar, buen punto, aunque eso no funciona para implementaciones de shell como pdksh/ mksh(y otros derivados de pdksh) o zshdonde set +osolo muestra las desviaciones de la configuración predeterminada. Eso funcionaría para bash / dash / yash pero no sería portátil.
Stéphane Chazelas
13

Puede leer la $-variable al principio para ver si -xestá configurada o no y luego guardarla en una variable, por ejemplo

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

Del manual de Bash :

($ -, un guión). Se expande a los indicadores de opción actuales como se especifica en la invocación, mediante el comando set incorporado, o los establecidos por el propio shell (como la opción -i).

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

En bash, $ SHELLOPTS se establece con las banderas que están activadas. Verifíquelo antes de encender xtrace, y reinicie xtrace solo si estaba apagado antes.

Jeff Schaller
fuente
5

Solo para decir lo obvio, si set -xva a estar vigente durante la duración de la secuencia de comandos, y esto es solo una medida de prueba temporal (no debe ser parte permanente de la salida), invoque la secuencia de comandos con la -xopción, por ejemplo,

$ bash -x path_to_script.sh

... o, cambie temporalmente el script (primera línea) para habilitar la salida de depuración agregando la -xopción:

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

Me doy cuenta de que esto probablemente es un golpe demasiado amplio para lo que quieres, pero es la forma más simple y rápida de habilitar / deshabilitar, sin complicar demasiado el script con cosas temporales que probablemente querrás eliminar de todos modos (en mi experiencia).

Miguel
fuente
3

Esto proporciona funciones para guardar y restaurar las marcas visibles a través del $-parámetro especial POSIX . Usamos la localextensión para variables locales. En un script portátil compatible con POSIX, se utilizarían variables globales (sin localpalabra clave):

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

Esto se usa de manera similar a cómo se guardan y restauran los indicadores de interrupción en el kernel de 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 ... */

Esto no funcionará para ninguna de las opciones extendidas que no están cubiertas en la $-variable.

Me acabo de dar cuenta de que POSIX tiene lo que estaba buscando: el +oargumento de setno es una opción, sino un comando que voltea un montón de comandos que, si son evalrestaurados, restaurarán las opciones. Entonces:

flags=$(set +o)

set -x

# ...

eval "$flags"

Eso es.

Un pequeño problema es que si la -xopción se activa antes de esto eval, set -ose ve una desagradable ráfaga de comandos. Para eliminar esto, podemos hacer lo siguiente:

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

Puedes usar un sub-shell.

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

Similar a lo que dijo @Jeff Shaller pero configurado para NO hacer eco de esa parte en el medio (que es lo que necesitaba):

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