Como restaurar o valor das opções de shell como `set -x`?

47

Quero set -xno início do meu script e "desfazer" (voltar ao estado antes de defini-lo) depois, em vez de definir cegamente +x. Isso é possível?

PS: Eu já verifiquei aqui ; isso não parecia responder à minha pergunta até onde eu sabia.

Roney Michael
fonte

Respostas:

59

Abstrato

Para reverter um set -xbasta executar a set +x. Na maioria das vezes, o inverso de uma string set -stré a mesma seqüência com um +: set +str.

Em geral, para restaurar todas as errexitopções de shell (leia abaixo sobre o bash ) (alteradas com o setcomando), você pode fazer (também leia abaixo sobre as shoptopções do bash ):

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

Descrição mais longa

bater

Este comando:

shopt -po xtrace

irá gerar uma cadeia de caracteres executável que reflete o estado da opção. O psinalizador significa imprimir, e o osinalizador especifica que estamos perguntando sobre as opções definidas pelo setcomando (em oposição às opções definidas pelo shoptcomando). Você pode atribuir essa sequência a uma variável e executar a variável no final do seu script para restaurar o 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 solução funciona para várias opções simultaneamente:

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"

A adição set +vxevita a impressão de uma longa lista de opções.


E, se você não listar nenhum nome de opção,

oldstate="$(shopt -po)"

fornece os valores de todas as opções. E, se você deixar de fora a obandeira, poderá fazer o mesmo com as shoptopções:

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

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

Se você precisar testar se uma setopção está definida, a maneira mais idiomática (Bash) de fazer isso é:

[[ -o xtrace ]]

o que é melhor que os outros dois testes semelhantes:

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

Com qualquer um dos testes, isso 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"

Veja como testar o estado de uma shoptopção:

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

Uma solução simples e compatível com POSIX para armazenar todas as setopções é:

set +o

que é descrito no padrão POSIX como:

+ o

    Escreva as configurações de opções atuais na saída padrão em um formato adequado para reinicialização no shell como comandos que atingem as mesmas configurações de opções.

Então, simplesmente:

oldstate=$(set +o)

preservará valores para todas as opções definidas usando o setcomando

Novamente, restaurar as opções para seus valores originais é uma questão de executar a variável:

set +vx; eval "$oldstate"

Isso é exatamente equivalente ao uso do Bash's shopt -po. Observe que ele não cobrirá todas as opções possíveis do Bash , pois algumas delas são definidas por shopt.

caso especial do bash

Há muitas outras opções do shell listados com shoptno 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

Eles podem ser anexados à variável definida acima e restaurados da mesma maneira:

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

É possível fazer (o $-é anexado para garantir a errexitpreservação):

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

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

Nota : cada shell possui uma maneira ligeiramente diferente de criar a lista de opções configuradas ou não (para não mencionar as opções diferentes definidas), portanto, as strings não são portáveis ​​entre shells, mas são válidas para o mesmo shell.

caso especial zsh

zshtambém funciona corretamente (após o POSIX) desde a versão 5.3. Nas versões anteriores, seguia o POSIX apenas parcialmente, set +ona medida em que imprimia opções em um formato adequado para reinicializar o shell como comandos, mas apenas para opções definidas (não imprimia opções não definidas ).

caso especial mksh

O mksh (e, consequentemente, lksh) ainda não é capaz (MIRBSD KSH R54 2016/11/11). O manual mksh contém:

Em uma versão futura, set + o comportará comandos compatíveis com POSIX e imprimirá para restaurar as opções atuais.

caso especial set -e

No bash, o valor de set -e( errexit) é redefinido dentro de subcascas, o que dificulta a captura de seu valor set +odentro de uma subcasca $ (…).

Como solução alternativa, use:

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

Com o shell Almquist e seus derivados ( pelo menos dashNetBSD / FreeBSD sh) e bash4.4 ou superior, você pode tornar as opções locais para uma função com local -(torne a $-variável local se desejar):

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

Isso não se aplica a origem arquivos, mas você pode redefinir sourcecomo source() { . "$@"; }para trabalho em torno disso.

Com ksh88, as alterações de opção são locais para a função por padrão. Com ksh93, esse é apenas o caso das funções definidas com a function f { ...; }sintaxe (e o escopo é estático comparado ao escopo dinâmico usado em outros shells, incluindo o ksh88):

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

Em zsh, isso é feito com a localoptionsopção:

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

POSIXly, você pode fazer:

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

No entanto, alguns shells produzirão a + 2> /dev/nullapós a restauração (e você verá o rastreio dessa caseconstrução, é claro, se set -xjá estiver ativado). Essa abordagem também não é reentrada (como se você fizer isso em uma função que se chama ou outra função que usa o mesmo truque).

Consulte https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh (escopo local para variáveis ​​e opções para shells POSIX) para saber como implementar uma pilha que contorna isso.

Com qualquer shell, você pode usar subshells para limitar o escopo das opções

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

No entanto, isso limita o escopo de tudo (variáveis, funções, aliases, redirecionamentos, diretório de trabalho atual ...), não apenas opções.

Stéphane Chazelas
fonte
o bash 4.4 saiu há 4 dias (16-Setembro-2016), então provavelmente você vai ter que recompilar-lo sozinho, mas por que não
edi9999
Parece-me que oldstate=$(set +o)é uma maneira mais simples (e POSIX) de armazenar todas as opções.
sorontar 20/09/16
@sorontar, bom ponto, porém, que não funciona para implementações de shell como pdksh/ mksh(e outros derivados pdksh) ou zshonde set +oapenas gera os desvios das configurações padrão. Isso funcionaria para o bash / dash / yash, mas não seria portátil.
Stéphane Chazelas
13

Você pode ler a $-variável no início para ver se -xestá definida ou não e depois salvá-la em uma variável, por exemplo

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

No manual do Bash :

($ -, um hífen.) Expande para os sinalizadores de opções atuais, conforme especificado na chamada, pelo comando set builtin ou pelos definidos pelo próprio shell (como a opção -i).

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

No bash, $ SHELLOPTS é definido com os sinalizadores ativados. Verifique-o antes de ativar o xtrace e redefina o xtrace apenas se estiver desativado antes.

Jeff Schaller
fonte
5

Apenas para declarar o óbvio, se set -xdeve ter efeito durante a duração do script, e esta é apenas uma medida de teste temporária (para não fazer parte permanentemente da saída), invoque o script com a -xopção, por exemplo,

$ bash -x path_to_script.sh

... ou, altere temporariamente o script (primeira linha) para ativar a saída de depuração adicionando a -xopção:

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

Sei que isso provavelmente é um golpe muito amplo para o que você deseja, mas é a maneira mais simples e rápida de ativar / desativar, sem complicar demais o script com coisas temporárias que você provavelmente deseja remover de qualquer maneira (na minha experiência).

Michael
fonte
3

Isso fornece funções para salvar e restaurar os sinalizadores visíveis através do $-parâmetro especial POSIX . Usamos a localextensão para variáveis ​​locais. Em um script portátil em conformidade com POSIX, variáveis ​​globais seriam usadas (sem localpalavra-chave):

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

Isso é usado de maneira semelhante à maneira como os sinalizadores de interrupção são salvos e restaurados no kernel do 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 ... */

Isso não funcionará para nenhuma das opções estendidas que não são cobertas na $-variável.

Acabei de notar que o POSIX tem o que eu estava procurando: o +oargumento de setnão é uma opção, mas um comando que despeja um monte de comandos que, se eval-ed, restauram as opções. Assim:

flags=$(set +o)

set -x

# ...

eval "$flags"

É isso aí.

Um pequeno problema é que, se a -xopção for ativada antes disso eval, um feio fluxo de set -ocomandos é visto. Para eliminar isso, podemos fazer o seguinte:

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

Você pode usar um sub-shell.

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