Alguém já tentou escrever um shell que corrige errexit ('set -e')?

2

errexit ( set -e ) é frequentemente sugerido como uma maneira de tornar scripts simples mais robustos. No entanto, é geralmente considerado como uma armadilha horrível por qualquer um que tenha visto como se comporta em scripts mais complexos, em particular com funções. E um problema muito semelhante pode ser visto com subshells. Veja o exemplo a seguir, adaptado do link

(
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 -e em casos específicos tenha variado historicamente de forma lamentável, 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 seguintes :

  • 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 FAILED para o código acima?

    Espero que isso também altere set -e , de modo que as expressões && de nível superior que retornam false sejam consideradas fatais. link E esperamos 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
    )
    

    Eu também suponho que as declarações aritméticas ( let builtin) seriam redefinidas de alguma forma que evita implicitamente coagir seu valor numérico para um status de saída verdadeiro / falso.

  2. Existe um exemplo de shell que faz isso?

    Eu 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 detectar erros. Eu não gosto de usar && como um separador de instrução, se isso significa acidentalmente omitir um separador de instrução 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 errexit acima da subcamada.

AFAICT isto é muito semelhante ao caso de teste (false; echo foo) || echo bar; echo \ $? da tabela aqui , 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 -e dentro da subshell - (set -e; false; echo foo) || echo bar; echo \ $? - há duas exceções adicionais, "SVR4 sh sun5.10" e dash-0.3.4.

Para o espírito da minha pergunta, suponho que ter set -e dentro da subcamada fosse uma distração. Meu principal interesse é em um idioma onde você usa set -e no topo do script, e também se aplica a subshells (que já é o comportamento do 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 schilytools , e que este resultado foi verificado a partir do release 2018-06-11. Talvez isso seja baseado em 1) set -e estando dentro da subcamada e 2) "SVR4 sh sun5.10". "osh" é "baseado nas fontes do OpenSolaris e, portanto, baseado em SVR4 e SVID3". Ou talvez haja alguma variação adicional horrível causada pela adição de && echo "OK" entre o subshell e o || echo "FAILED" .

Eu não acho que a shell schily responda a minha pergunta. Você poderia 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 você não pode modificar variáveis globais. Pelo menos, ainda não vi ninguém defender isso como um estilo de codificação de propósito geral.

    
por sourcejedi 02.06.2018 / 20:57

1 resposta

1

O padrão POSIX (cf -e ) é mais claro que o manual do Bash sobre a opção errexit shell.

When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit, as if by executing the exit special built-in utility with no arguments, with the following exceptions:

  1. The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.

  2. The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

  3. If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.

This requirement applies to the shell environment and each subshell environment separately. For example, in:

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

the false command causes the subshell to exit without executing echo one; however, echo two is executed because the exit status of the pipeline (false; echo one) | cat is zero.

Claramente, o código de amostra é equivalente ao seguinte pseudocódigo.

( LIST; ) && COMMAND || COMMAND

O status de saída da lista é zero porque:

  • a opção errexit shell é ignorada.

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

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

    
por 03.06.2018 / 19:37