Según este manual de referencia:
-E (también -o errtrace)
Si se establece, cualquier trampa en ERR es heredada por funciones de shell, sustituciones de comandos y comandos ejecutados en un entorno de subshell. La trampa
ERR normalmente no se hereda en tales casos.
Sin embargo, debo estar interpretándolo mal, porque lo siguiente no funciona:
#!/usr/bin/env bash
# -*- bash -*-
set -e -o pipefail -o errtrace -o functrace
function boom {
echo "err status: $?"
exit $?
}
trap boom ERR
echo $( made up name )
echo " ! should not be reached ! "
Ya conozco la asignación simple, my_var=$(made_up_name)
, saldrá del script con set -e
(es decir, errexit).
Es -E/-o errtrace
¿Se supone que funciona como el código anterior? ¿O, lo más probable, lo leí mal?
Respuesta aceptada:
Nota:zsh
se quejará de "malos patrones" si no lo configura para aceptar "comentarios en línea" para la mayoría de los ejemplos aquí y no los ejecuta a través de un shell de proxy como lo he hecho con sh <<-CMD
.
Ok, entonces, como dije en los comentarios anteriores, no sé específicamente sobre el set -E
de bash , pero sé que los shells compatibles con POSIX brindan un medio simple de probar un valor si lo desea:
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works"
}
_test && echo "_test doesnt fail"
# END
CMD
sh: line 1: empty: error string
+ echo
+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail
Arriba verá que aunque usé parameter expansion
para probar ${empty?} _test()
todavía return
s un pase, como se evidencia en el último echo
Esto ocurre porque el valor fallido mata el $( command substitution )
subshell que lo contiene, pero su shell principal:_test
en este momento - sigue en camiones. Y echo
no le importa:está muy feliz de servir solo un newline; echo
es no una prueba.
Pero considera esto:
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
_test ||
echo "this doesnt even print"
# END
CMD
_test+ sh: line 1: empty: function doesnt run
Porque alimenté _test()'s
entrada con un parámetro preevaluado en el INIT here-document
ahora el _test()
La función ni siquiera intenta ejecutarse en absoluto. Además, el sh
Shell aparentemente se da por vencido por completo y echo "this doesnt even print"
ni siquiera se imprime.
Probablemente eso no lo que quieras.
Esto sucede porque ${var?}
La expansión de parámetros de estilo está diseñada para salir del shell
en caso de que falte un parámetro, funciona así:
${parameter:?[word]}
Indicar error si Null
o Unset.
Si el parámetro no está establecido o es nulo, la expansion of word
(o un mensaje que indique que no está configurado si se omite la palabra) se written to standard error
y el shell exits with a non-zero exit status
. De lo contrario, se sustituirá el valor del parámetro parameter shall be substituted
. Un shell interactivo no necesita salir.
No copiaré/pegaré todo el documento, pero si desea un error para un set but null
valor usas el formulario:
${var
error message }
Con los :colon
como anteriormente. Si desea un null
valor para tener éxito, simplemente omita los dos puntos. También puede negarlo y fallar solo para valores establecidos, como mostraré en un momento.
Otra ejecución de _test():
sh <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
echo "this runs" |
( _test ; echo "this doesnt" ) ||
echo "now it prints"
# END
CMD
this runs
sh: line 1: empty: function doesnt run
now it prints
Esto funciona con todo tipo de pruebas rápidas, pero arriba verás que _test()
, ejecutar desde la mitad de la pipeline
falla, y de hecho contiene command list
subshell falla por completo, ya que ninguno de los comandos dentro de la función se ejecuta ni el siguiente echo
ejecutar en absoluto, aunque también se muestra que se puede probar fácilmente porque echo "now it prints"
ahora imprime.
El diablo está en los detalles, supongo. En el caso anterior, el shell que sale es no el script _main | logic | pipeline
pero el ( subshell in which we ${test?} ) ||
por lo que se requiere un poco de sandboxing.
Y puede que no sea obvio, pero si quisieras pasar solo por el caso contrario, o solo set=
valores, también es bastante simple:
sh <<-CMD
N= #N is NULL
_test=$N #_test is also NULL and
v="something you would rather do without"
( #this subshell dies
echo "v is ${v+set}: and its value is ${v:+not NULL}"
echo "So this ${_test:-"$_test:="} will equal ${_test:="$v"}"
${_test:+${N:?so you test for it with a little nesting}}
echo "sure wish we could do some other things"
)
( #this subshell does some other things
unset v #to ensure it is definitely unset
echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
echo "So this ${_test:-"$_test:="} will equal NULL ${_test:="$v"}"
${_test:+${N:?is never substituted}}
echo "so now we can do some other things"
)
#and even though we set _test and unset v in the subshell
echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
# END
CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without
El ejemplo anterior aprovecha las 4 formas de sustitución de parámetros POSIX y sus diversos :colon null
o not null
pruebas Hay más información en el enlace de arriba, y aquí está de nuevo.
Y supongo que deberíamos mostrar nuestro _test
función de trabajo, también, ¿verdad? Simplemente declaramos empty=something
como parámetro de nuestra función (o en cualquier momento antes):
sh <<-CMD
_test() { echo $( echo ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?tested as a pass before function runs}
INIT
echo "this runs" >&2 |
( empty=not_empty _test ; echo "yay! I print now!" ) ||
echo "suspiciously quiet"
# END
CMD
this runs
not_empty
echo still works
yay! I print now!
Cabe señalar que esta evaluación es independiente:no requiere ninguna prueba adicional para fallar. Un par de ejemplos más:
sh <<-CMD
empty=
${empty?null, no colon, no failure}
unset empty
echo "${empty?this is stderr} this is not"
# END
CMD
sh: line 3: empty: this is stderr
sh <<-CMD
_input_fn() { set -- "[email protected]" #redundant
echo ${*?WHERES MY DATA?}
#echo is not necessary though
shift #sure hope we have more than $1 parameter
: ${*?WHERES MY DATA?} #: do nothing, gracefully
}
_input_fn heres some stuff
_input_fn one #here
# shell dies - third try doesnt run
_input_fn you there?
# END
CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?
Y finalmente volvemos a la pregunta original:cómo manejar los errores en un $(command substitution)
subcapa? La verdad es que hay dos formas, pero ninguna es directa. El núcleo del problema es el proceso de evaluación del shell:las expansiones del shell (incluidas $(command substitution)
) ocurren antes en el proceso de evaluación del shell que en la ejecución actual del comando del shell, que es cuando sus errores pueden detectarse y atraparse.
El problema que experimenta la operación es que en el momento en que el shell actual evalúa los errores, el $(command substitution)
la subcapa ya se ha sustituido; no quedan errores.
Entonces, ¿cuáles son las dos formas? O lo hace explícitamente dentro del $(command substitution)
subshell con pruebas como lo haría sin él, o absorbe sus resultados en una variable de shell actual y prueba su valor.
Método 1:
echo "$(madeup && echo : || echo '${fail:?die}')" |
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Método 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Esto
fallará independientemente del número de variables declaradas por línea:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
Y nuestro valor de retorno permanece constante:
echo $?
1
AHORA LA TRAMPA:
trap 'printf %s\n trap resurrects shell!' ERR
v1="$(madeup)" v2="$(printf %s\n shown after trap)"
echo "${v1:?#1 - still stderr}" "${v2:?invisible}"
sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap
echo $?
0