Como salvar e restaurar todas as opções de shell, incluindo errexit

6

Eu li muitas perguntas em vários sites de troca de pilha e fóruns de ajuda unix sobre como modificar opções de shell e restaurá-las -  o mais abrangente que encontrei aqui é em Como desfazer um conjunto -x '?

A sabedoria recebida parece ser a de salvar o resultado de set +o ou shopt -po e então eval depois para restaurar as configurações anteriores.

No entanto, no meu próprio teste com o bash 3.xe 4.x, a opção errexit não é salva corretamente ao fazer a substituição do comando.

Aqui está um script de exemplo para mostrar o problema:

set -o errexit
set -o nounset
echo "LOCAL SETTINGS:"
set +o
OLDOPTS=$(set +o)
echo
echo "SAVED SETTINGS:"
echo "$OLDOPTS"

E saída (aparentei algumas das variáveis irrelevantes):

LOCAL SETTINGS:
set -o errexit
set -o nounset

SAVED SETTINGS:
set +o errexit
set -o nounset

Isso parece extremamente perigoso. A maioria dos scripts que escrevo depende de errexit para interromper a execução se algum comando falhar. Acabei de localizar um bug em um dos meus scripts causado por isso, em que a função que deveria restaurar errexit no final acabou cancelando, retornando ao padrão desligado para o duração do script.

O que eu gostaria de poder fazer é escrever funções que possam definir opções conforme necessário e depois restaurar todas as opções corretamente antes de sair. Mas parece que no subshell invocado pela substituição do comando, errexit não é herdado.

Não sei como salvar o resultado de set +o sem usar a substituição de comandos ou saltar através de aros FIFO. Eu posso ler a partir de $SHELLOPTS , mas não é gravável ou no formato eval -able.

Eu sei que uma alternativa é usar uma função subshell , mas isso introduz muitas dores de cabeça para ser capaz de logar saída, bem como passar várias variáveis.

Provavelmente relacionado: link (parece que há uma solução alternativa para o bash 4.4, mas eu prefiro ter uma solução portátil)

    
por Eliot 02.08.2017 / 22:06

4 respostas

5

O que você está fazendo deve funcionar. Mas o bash desativa a opção errexit nas substituições de comandos, por isso preserva todas as opções, exceto esta. Isso é específico para o bash e específico para a opção errexit . O Bash preserva errexit ao executar no modo POSIX. Desde o bash 4.4, o bash também não limpa errexit em uma substituição de comando se shopt -s inherit_errexit estiver em vigor.

Como a opção está desativada antes de qualquer código ser executado dentro da substituição do comando, você deve verificar isso fora.

OLDOPTS=$(set +o)
case $- in
  *e*) OLDOPTS="$OLDOPTS; set -e";;
  *) OLDOPTS="$OLDOPTS; set +e";;
esac

Se você não gosta dessa complexidade, use o zsh.

setopt local_options
    
por 03.08.2017 / 03:26
1

errexit é propagado para substituições de processos.

set -e

# Backup restore commands into an array
declare -a OPTS
readarray -t OPTS < <(shopt -po)

set +e

# Restore options
declare cmd
for cmd in "${OPTS[@]}"; do
    eval "$cmd"
done

Verifique:

$  shopt -po errexit
set -o errexit

Versão do Bash:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
    
por 02.09.2018 / 01:52
1

A solução simples é anexar a configuração errexit a OLDOPTS :

OLDOPTS="$(set +o)"
[ "${BASH_VERSION:+x}" ] && shopt -qo errexit && OLDOPTS+=";set -e" || true

Feito.

    
por 20.10.2018 / 18:15
0

Depois de testar o antigo acima no alpine 3.6, agora adotei a seguinte abordagem mais simples:

OLDOPTS="$(set +o); set -${-//c}"
set -euf -o pipefail

... my stuff

# restore original options
set +vx; eval "${OLDOPTS}"

de acordo com a documentação, "$ -" contém a lista de opções atualmente ativas. Parece funcionar muito bem, estou faltando alguma coisa?

    
por 20.10.2018 / 12:43

Tags