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

40

Eu quero set -x no começo do meu script e "desfaz" (volte ao estado antes de defini-lo) depois, em vez de definir cegamente +x . Isso é possível?

P.S .: eu já verifiquei aqui ; que não parece responder a minha pergunta, tanto quanto eu poderia dizer.

    
por Roney Michael 20.09.2016 / 03:05

6 respostas

50

Resumo

Para reverter um set -x , basta executar um set +x . Na maioria das vezes, o contrário de uma string set -str é a mesma string com um + : set +str .

Em geral, para restaurar todas as opções de shell (leia abaixo sobre o bash errexit ) (alteradas com o comando set ) você pode fazer (também leia abaixo sobre bash shopt options):

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

Descrição mais longa

bash

Este comando:

shopt -po xtrace

irá gerar uma string executável que reflete o estado da opção. O sinalizador p significa print e o sinal o especifica que estamos perguntando sobre as opções configuradas pelo comando set (em oposição às opções configuradas pelo comando shopt ). Você pode atribuir essa string 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"

Adicionar set +vx evita 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 bandeira o , você pode fazer as mesmas coisas com shopt options:

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

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

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

[[ -o xtrace ]]

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 opção shopt :

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 opções set é:

set +o

que é descrito no padrão POSIX como:

+o

    Write the current option settings to standard output in a format that is suitable for reinput to the shell as commands that achieve the same options settings.

Então, simplesmente:

oldstate=$(set +o)

preservará os valores de todas as opções definidas usando o comando set .

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

set +vx; eval "$oldstate"

Isso equivale exatamente ao uso do shopt -po do Bash. Note que não cobrirá todas possíveis Bash opções, já que algumas delas são definidas por shopt .

caso especial de bash

Existem muitas outras opções de shell listadas com shopt no 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 que errexit seja preservado):

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

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

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

caso especial zsh

zsh também funciona corretamente (seguindo o POSIX) desde a versão 5.3. Nas versões anteriores, ele seguia o POSIX apenas parcialmente com set +o em que ele imprimia as opções em um formato adequado para reinputá-las como comandos, mas apenas para as opções set (não foi impresso < em> un-set opções).

caso especial mksh

O mksh (e por conseqüência lksh) ainda não é capaz (MIRBSD KSH R54 2016/11/11) de fazer isso. O manual do mksh contém isto:

In a future version, set +o will behave POSIX compliant and print commands to restore the current options instead.

set -e caso especial

No bash, o valor de set -e ( errexit ) é reconfigurado dentro de sub-shells, o que torna difícil capturar seu valor com set +o dentro de um sub-shell $ (…).

Como solução alternativa, use:

oldstate="$(set +o); set -$-"
    
por 20.09.2016 / 03:57
18

Com o shell e derivados Almquist ( dash , NetBSD / FreeBSD sh pelo menos) e bash 4.4 ou superior, você pode tornar opções locais para uma função com local - (torne a variável $- local, se quiser):

$ 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 arquivos originados , mas você pode redefinir source as source() { . "$@"; } para contornar isso.

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

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

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

$ 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 emitem um + 2> /dev/null após a restauração (e você verá o rastreio dessa case de construção se o set -x já estiver habilitado). Essa abordagem também não é reentrante (como se você fizesse isso em uma função que chama a si mesma ou outra função que usa o mesmo truque).

Veja o link (escopo local para variáveis e opções para shells POSIX) como implementar uma pilha que funcione em torno disso.

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.

    
por 20.09.2016 / 11:57
13

Você pode ler a variável $- no início para ver se -x está definido ou não e, em seguida, salvá-lo em uma variável, por exemplo,

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

No Manual de Bash :

($-, a hyphen.) Expands to the current option flags as specified upon invocation, by the set builtin command, or those set by the shell itself (such as the -i option).

    
por 20.09.2016 / 03:26
7
[[ $SHELLOPTS =~ xtrace ]] && wasset=1
set -x
echo rest of your script
[[ $wasset -eq 0 ]] && set +x

No bash, $ SHELLOPTS é definido com o sinalizadores que estão ativados. Verifique antes de ligar o xtrace e redefinir o xtrace somente se estiver desativado antes.

    
por 20.09.2016 / 03:23
5

Apenas para declarar o óbvio, se set -x estiver em vigor pela duração do script, e isso é apenas uma medida de teste temporária (para não ser permanentemente parte da saída), então invoque o script w / a opção -x , por exemplo,

$ bash -x path_to_script.sh

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

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

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

    
por 20.09.2016 / 09:33
3

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

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

Isto é usado de forma semelhante a 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á em nenhuma das opções estendidas que não são cobertas na variável $- .

Só notei que POSIX tem o que eu estava procurando: o argumento +o de set não é uma opção, mas um comando que despeja um monte de comandos que, se eval -ed irá restaurar as opções. Então:

flags=$(set +o)

set -x

# ...

eval "$flags"

É isso.

Um pequeno problema é que, se a opção -x for ativada antes desse eval , uma enxurrada feia de comandos set -o será vista. Para eliminar isso, podemos fazer o seguinte:

set +x             # turn off trace not to see the flurry of set -o.
eval "$flags"      # restore flags
    
por 20.09.2016 / 20:45

Tags