Alguém já tentou escrever um shell que conserte o errexit (`set -e`)?

Alguém já tentou escrever um shell que conserte o errexit (`set -e`)?

errexit( set -e) é frequentemente sugerido como uma forma de tornar scripts simples mais robustos. No entanto, é geralmente considerado uma armadilha horrível por qualquer pessoa que tenha visto como ele se comporta em scripts mais complexos, em particular com funções. E um problema muito semelhante pode ser visto com subshells. Tomemos o seguinte exemplo, adaptado dehttps://stackoverflow.com/questions/29926013/exit-subshell-on-error

(
set -o errexit
false
true
) && echo "OK" || echo "FAILED";

A armadilha aqui é que os shells mostram "OK" e não "FAILED".[*]

Embora o comportamento de set -eem casos específicos tenha variado historicamente de maneiras infelizes, os shells disponíveis em uma distribuição moderna afirmam seguir o padrão de shell POSIX, e os testes mostraram o mesmo comportamento ("OK") para todos os itens a seguir:

  • bash-4.4.19-2.fc28.x86_64(Fedora)
  • busybox-1.26.2-3.fc27.x86_64(Fedora)
  • dash 0.5.8-2.4(Debian 9)
  • zsh 5.3.1-4+b2(Debian 9)
  • posh 0.12.6+b1(Debian 9)
  • ksh 93u+20120801-3.1(Debian 9).

Pergunta

  1. Existe alguma razão técnica pela qual você não pode escrever um shell com recursos semelhantes ao POSIX sh, exceto que ele imprime FAILEDo código acima?

    Espero que isso também mude set -epara que as expressões de nível superior &&que retornam falso sejam consideradas fatais. https://serverfault.com/a/847016/133475 E espero que tenha um idioma melhor para usar com o grep.

    set -e
    
    # print matching lines, but it's not an error if there are no matches in the file
    (
    set +e
    grep pattern file.txt
    RET=$?
    [[ $RET == 0 || $RET == 1 ]] || exit $RET
    )
    

    Acho que também presumo que as instruções aritméticas ( letconstruídas) seriam redefinidas de alguma forma que evitasse coagir implicitamente seu valor numérico para um status de saída verdadeiro/falso.

  2. Existe um exemplo de shell que faz isso?

    Não me importo de olhar para algo com uma sintaxe diferente de Bourne. Estou interessado em algo que seja compacto, ao escrever scripts curtos de "cola", mas que também tenha uma estratégia compacta para detecção de erros. Não gosto de usar &&como separador de instruções, se isso significar que a omissão acidental de um separador de instruções fará com que os erros sejam ignorados silenciosamente.


[*] EDITAR. Este talvez não seja um exemplo perfeito para usar. A discussão sugere que um exemplo melhor seria mover o set -o errexitacima do subshell.

AFAICT isso é muito semelhante ao caso de teste (false; echo foo) || echo bar; echo \ $?da tabelaaqui, que diz que mostraria "foo" (e depois "0") no shell Bourne original e na maioria de seus descendentes. As exceções são "hist.ash" e bash 1.14.7.

Quando você adiciona set -edentro do subshell - (set -e; false; echo foo) || echo bar; echo \ $?- há duas exceções adicionais, "SVR4 sh sun5.10" e dash-0.3.4.

Pelo espírito da minha pergunta, suponho que estar set -edentro do subshell foi uma distração. Meu principal interesse é um idioma que você usa set -ena parte superior do script e também se aplica a subshells (que já é o comportamento POSIX).

Jörg Schilling sugere que o shell Bourne do Unix original imprimiria "FAILED" para o exemplo que usei nesta pergunta, que ele portou esse shell para POSIX como"osh" em scilytools, e que esse resultado foi verificado a partir do lançamento 11/06/2018. Talvez isso se baseie em 1) set -eestar dentro do subshell e 2) "SVR4 sh sun5.10". "osh" é "baseado nas fontes OpenSolaris e, portanto, baseado em SVR4 e SVID3". Ou talvez haja alguma variação adicional horrível causada pela adição && echo "OK"entre o subshell e || echo "FAILED".

Não creio que a concha esquisita responda à minha pergunta. Você pode usar subshells para funções (usar (no lugar de {para executar a função em um subshell) e iniciar cada subshell com set -e. No entanto, o uso de subshells apresenta a limitação de que não é possível modificar variáveis ​​globais. Pelo menos, ainda não vi ninguém defender isso como um estilo de codificação de uso geral.

Responder1

OPadrão POSIX(cf.-e) é mais claro que o manual do Bash sobre a errexitopção shell.

Quando esta opção estiver ativada, quando qualquer comando falhar (por qualquer um dos motivos listados em Consequências de Erros do Shell ou retornando um status de saída maior que zero), o shell sairá imediatamente, como se estivesse executando o utilitário interno especial de saída sem argumentos, com as seguintes exceções:

  1. A falha de qualquer comando individual em um pipeline multicomando não deve causar a saída do shell. Somente a falha do próprio gasoduto será considerada.

  2. A configuração -e deve ser ignorada ao executar a lista composta após a palavra reservada while, Until, if ou elif, um pipeline começando com ! palavra reservada ou qualquer comando de uma lista AND-OR diferente do último.

  3. Se o status de saída de um comando composto diferente de um comando subshell foi o resultado de uma falha enquanto -e estava sendo ignorado, então -e não se aplicará a este comando.

Este requisito se aplica ao ambiente shell e a cada ambiente subshell separadamente. Por exemplo, em:

set -e; (false; echo one) | cat; echo two

o comando false faz com que o subshell saia sem executar echo one; entretanto, o echo dois é executado porque o status de saída do pipeline (falso; echo um) | gato é zero.

Claramente, o código de exemplo é equivalente ao pseudocódigo a seguir.

( LIST; ) && COMMAND || COMMAND

O status de saída dolista é zero porque:

  • a errexitopção shell é ignorada.

  • o status de retorno é o status de saída do último comando especificado nolista, aqui true.

Portanto, a segunda parte da lista AND é executada: echo "OK".

Responder2

Não uma resposta à pergunta 'Alguém tentou escrever...', mas algumas reflexões sobre como isso poderia ser feito. Curioso para saber se alguém pode fornecer feedback.

A abordagem comum nas soluções de script atuais (Python, JavaScript e o antigo Perl e similares) é usar blocos try ... catch. O comportamento esperado é que qualquer erro acione uma exceção. Esse comportamento está em conflito com o comportamento *sh em que os erros são capturados no $?. Claramente, o '-e' falhou em acertar de forma que funcione como a maioria dos desenvolvedores espera. Não há necessidade de entrar em detalhes aqui, pois isso já está extensivamente documentado.

Duas abordagens possíveis:

  • Crie um melhor set -e: por exemplo, set -ocatcherrcom melhor tratamento de erros.
  • Adicione um novo try { list ; }comando, seguindo soluções de script modernas

O '-ofallerr` pode ser definido da seguinte forma:

  • Se algum comando falhar fora do fluxo de controle (se/enquanto/até), o contexto de execução atual deverá retornar 1 (ou sair se este for o bloco de nível superior). Essa mudança forçará efetivamente o desenvolvedor a fornecer tratamento de erros em qualquer comando que falhe ou comando marcado explicitamente com erros que podem ser ignorados.
  • Funciona para scripts simples, efetivamente, aborta em caso de erro não tratado.

A try { list ; }abordagem é semelhante, apenas sintaxe diferente

Exemplo:

function foo {
    cat $* > a.txt
    wc -l a.txt
}

set -oerrfail
foo /non/existing/file    # Will  fail, without executing the "wc -l"

if foo /non/existing/file ; then
   ...
else
   # This is the "catch" block
fi

Com um bloco try:

try {
    foo /non/existing/file
    ...
} || { catch-block }

Efetivamente, o try é equivalente a "set -oerrofail", executando a lista de comandos sob um 'if! {...}; então {catch-bloco}

informação relacionada