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

9

Eu li muitas perguntas em vários sites de troca de pilhas e fóruns de ajuda do unix sobre como modificar opções de shell e depois restaurá-las - a mais abrangente que encontrei aqui é em Como "desfazer" um `set -x`?

A sabedoria recebida parece ser a de salvar tanto fora o resultado de set +oou shopt -poe, em seguida, evalele mais tarde para restaurar as configurações anteriores.

No entanto, em meus próprios testes com o bash 3.xe 4.x, a errexitopção não é salva corretamente ao fazer a substituição de comandos.

Aqui está um exemplo de script 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 (cortei 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 errexitpara interromper a execução se algum comando falhar. Eu apenas localizei um bug em um dos meus scripts causado por isso, onde a função que deveria restaurar errexitno final acabou substituindo-a, retornando-a ao padrão de off durante a duração do script.

O que eu gostaria de poder fazer é escrever funções que podem definir opções conforme necessário e depois restaurar todas as opções corretamente antes de sair. Mas parece que no sub-invocador invocado pela substituição de comando, errexitnão é herdado.

Estou sem saber como salvar o resultado set +osem usar a substituição de comandos ou pular os aros FIFO. Eu posso ler, $SHELLOPTSmas não é evalum formato gravável ou inativável.

Eu sei que uma alternativa é usar uma função subshell , mas isso introduz muitas dores de cabeça por poder registrar a saída e também transmitir várias variáveis.

Provavelmente relacionado: /programming/29532904/bash-subshell-errexit-semantics (parece que existe uma solução alternativa para o bash 4.4 e superior, mas eu prefiro ter uma solução portátil)

Eliot
fonte
Isso não restaura as shoptopções de conjunto do bash (como nullglob).
Isaac

Respostas:

6

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

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

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

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

setopt local_options
Gilles 'SO- parar de ser mau'
fonte
3
Observe que bash4.4agora tem local -(à la ash) como equivalente a zsh's setopt localoptions(ou o que o ksh88 faz por padrão) #
6601 Stéphane Chazelas
Veja também shopt -s lastpipe; set +o | IFS= read -rd '' OLDOPTS || :(os dois conjuntos de opções são outra das bashidiossincrasias de).
Stéphane Chazelas
Mais simples: OLDOPTS="$(set +o); set -$-".
Isaac
2

Depois de experimentar o anterior no alpine 3.6, agora adotei a seguinte abordagem muito 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, "$ -" mantém a lista de opções ativas no momento. Parece funcionar muito bem, estou perdendo alguma coisa?

Erich Eichinger
fonte
@Isaac Eu uso 'set + euf' porque $ - contém apenas a lista de opções ativas . ou seja, ao redefinir, desativei tudo primeiro e depois reativei apenas a lista de opções ativas anteriormente (eu escolho 'euf' porque essas são as únicas opções que geralmente modifico - para uso geral, provavelmente deve conter a lista completa de opções possíveis). Bom ponto sobre 'set + vx', modifiquei o exemplo acima em conformidade #
Erich Eichinger
que o trabalho pode, por errexit, mas não, por exemplo, para a verificação de limites ou englobamento ( set -uf)
Erich Eichinger
@ Isaac Estou corrigido. Não sei o que fiz de errado. Muito obrigado pela sua ajuda! Também bom ver que você correu para dentro e resolveu o problema -c flag - Eu gostaria de ter visto o seu comentário mais cedo;)
Erich Eichinger
Eu atualizei acima solução com base no feedback @Isaac 's
Erich Eichinger
1

errexit é propagado em substituições de processo.

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

Verifica:

$  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)
Sergej Alikov
fonte
1

A solução simples é anexar a errexitconfiguração a OLDOPTS:

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

Feito.

Isaac
fonte
Veja também [[ -o errexit ]](ksh / bash / zsh) e [ -o errexit ](bash / ksh / yash)
Stéphane Chazelas
O shoptcomando é válido para o bash, não há razão significativa para evitá-lo (eu sou apenas uma questão de sua preferência pessoal de como as respostas devem ser escritas). Não é uma mudança significativa. @ StéphaneChazelas
Isaac