¿Alguien ha intentado escribir un shell que solucione el error (`set -e`)?

¿Alguien ha intentado escribir un shell que solucione el error (`set -e`)?

errexit( set -e) se sugiere a menudo como una forma de hacer que los scripts simples sean más robustos. Sin embargo, cualquiera que haya visto cómo se comporta en scripts más complejos, en particular con funciones, lo considera una trampa horrible. Y se puede observar un problema muy similar con las subcapas. Tomemos el siguiente ejemplo, adaptado dehttps://stackoverflow.com/questions/29926013/exit-subshell-on-error

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

La trampa aquí es que los shells muestran "OK" y no "FAILED".[*]

Si bien el comportamiento set -een casos específicos ha variado históricamente de manera desafortunada, los shells disponibles en una distribución moderna afirman seguir el estándar de shell POSIX, y las pruebas mostraron el mismo comportamiento ("OK") para todo lo siguiente:

  • 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).

Pregunta

  1. ¿Existe alguna razón técnica por la que no se puede escribir un shell con características similares a POSIX sh, excepto que imprime FAILEDpara el código anterior?

    Espero que también cambie set -epara que las expresiones de nivel superior &&que devuelven falso se consideren fatales. https://serverfault.com/a/847016/133475 Y con suerte tendría algún lenguaje más agradable para usar con 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
    )
    

    Supongo que también asumo que las declaraciones aritméticas ( letintegradas) se redefinirían de alguna manera que evite forzar implícitamente su valor numérico a un estado de salida verdadero/falso.

  2. ¿Existe un ejemplo de un shell que haga algo de esto?

    No me importa mirar algo con una sintaxis diferente a la de Bourne. Estoy interesado en algo que sea compacto al escribir scripts cortos de "pegamento", pero que también tenga una estrategia compacta para detectar errores. No me gusta usarlo &&como separador de declaraciones, si eso significa que omitir accidentalmente un separador de declaraciones hará que los errores se ignoren silenciosamente.


[*] EDITAR. Quizás este no sea un ejemplo perfecto para usar. La discusión sugiere que un mejor ejemplo sería mover el set -o errexitsubnivel por encima.

AFAICT esto es muy similar al caso de prueba (false; echo foo) || echo bar; echo \ $?de la tablaaquí, que dice que mostraría "foo" (y luego "0") en el shell Bourne original y en la mayoría de sus descendientes. Las excepciones son "hist.ash" y bash 1.14.7.

Cuando agrega set -edentro del subshell - (set -e; false; echo foo) || echo bar; echo \ $?- hay dos excepciones adicionales, "SVR4 sh sun5.10" y dash-0.3.4.

Para el espíritu de mi pregunta, supongo que tener set -edentro de la subcapa fue una distracción. Mi principal interés es un modismo que se utiliza set -een la parte superior del script y que también se aplica a los subshells (que ya es el comportamiento POSIX).

Jörg Schilling sugiere que el shell Bourne del Unix original imprimiría "FAILED" para el ejemplo que utilicé en esta pregunta, que ha portado este shell a POSIX como"osh" en schilytools, y que este resultado ha sido verificado a partir de la versión 2018-06-11. Quizás esto se base en 1) set -eestar dentro de la subcapa y 2) "SVR4 sh sun5.10". "osh" está "basado en las fuentes de OpenSolaris y, por tanto, en SVR4 y SVID3". O tal vez haya alguna variación adicional horrible causada al agregar && echo "OK"entre el subnivel y || echo "FAILED".

No creo que el caparazón esquivo responda a mi pregunta. Puede usar subcapas para funciones (usar (en lugar de {para ejecutar la función en una subcapa) y comenzar cada subcapa con set -e. Sin embargo, el uso de subcapas introduce la limitación de que no se pueden modificar las variables globales. Al menos, todavía tengo que ver a alguien defender esto como un estilo de codificación de propósito general.

Respuesta1

Elestándar POSIX(ver-mi) es más claro que el manual de Bash sobre la errexitopción de shell.

Cuando esta opción está activada, cuando falla cualquier comando (por cualquiera de los motivos enumerados en Consecuencias de los errores del Shell o al devolver un estado de salida mayor que cero), el Shell se cerrará inmediatamente, como si se ejecutara la utilidad especial integrada de salida. sin argumentos, con las siguientes excepciones:

  1. El fallo de cualquier comando individual en una canalización de comandos múltiples no provocará la salida del shell. Sólo se considerará el fallo de la propia tubería.

  2. La configuración -e se ignorará al ejecutar la lista compuesta que sigue a la palabra reservada while, Until, if o elif, una canalización que comienza con ! palabra reservada, o cualquier comando de una lista AND-OR distinto del último.

  3. Si el estado de salida de un comando compuesto que no sea un comando de subshell fue el resultado de una falla mientras se ignoraba -e, entonces -e no se aplicará a este comando.

Este requisito se aplica al entorno de shell y a cada entorno de subshell por separado. Por ejemplo, en:

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

el comando false hace que el subshell salga sin ejecutar echo one; sin embargo, el eco dos se ejecuta porque el estado de salida de la canalización (falso; eco uno) | gato es cero.

Claramente, el código de muestra es equivalente al siguiente pseudocódigo.

( LIST; ) && COMMAND || COMMAND

El estado de salida dellista es cero porque:

  • la errexitopción de shell se ignora.

  • el estado de retorno es el estado de salida del último comando especificado en ellista, aquí true.

Por tanto, se ejecuta la segunda parte de la lista AND: echo "OK".

Respuesta2

No es una respuesta a la pregunta "¿Alguien ha intentado escribir...", sino algunas ideas sobre cómo se podría hacer? Es curioso si alguien puede dar su opinión.

El enfoque común en las soluciones de secuencias de comandos actuales (Python, JavaScript y el antiguo Perl, y similares) es utilizar bloques try... catch. El comportamiento esperado es que cualquier error desencadene una excepción. Este comportamiento está en conflicto con el comportamiento *sh de que el error se captura en $?. Claramente, la '-e' no logró hacerlo bien de tal manera que funcione como esperan la mayoría de los desarrolladores. No es necesario dar más detalles aquí, ya que esto ya está ampliamente documentado.

Dos enfoques posibles:

  • Cree una mejor set -e: por ejemplo, set -ocatcherrcon un mejor manejo de errores.
  • Agregue un nuevo try { list ; }comando, siguiendo las soluciones de script modernas

El '-ofailerr` se puede definir de la siguiente manera:

  • Si algún comando falla fuera del flujo de control (si/mientras/hasta), el contexto de ejecución actual debe devolver 1 (o salir si este es el bloque de nivel superior). Este cambio obligará efectivamente al desarrollador a proporcionar manejo de errores en cualquier comando que falle, o comando marcado explícitamente con errores que puedan ignorarse.
  • Funciona para scripts simples, efectivamente, cancela ante un error no controlado.

El try { list ; }enfoque es similar, solo que la sintaxis es diferente.

Ejemplo:

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

Con un bloque de prueba:

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

Efectivamente, try es equivalente a "set -oerrofail", ejecutando la lista de comandos bajo un comando 'if! {...}; entonces {bloquear captura}

información relacionada