El lenguaje exacto utilizado en la especificación Single UNIX para describir el significado deset -e
es:
Cuando esta opción está activada, si un comando simple falla por alguno de los motivos enumerados en Consecuencias de los errores del shell o devuelve un valor de estado de salida> 0, y no es [un comando condicional o negado], el shell se cerrará inmediatamente.
Existe una ambigüedad en cuanto a lo que sucede cuando dicho comando se produce en una subshell . Desde un punto de vista práctico, todo lo que el subshell puede hacer es salir y devolver un estado distinto de cero al shell principal. El hecho de que el shell primario salga a su vez depende de si este estado distinto de cero se traduce en un simple comando que falla en el shell primario.
Uno de estos casos problemáticos es el que encontró: un estado de retorno distinto de cero de una sustitución de comando . Como este estado se ignora, no hace que el shell principal salga. Como ya descubrió , una forma de tener en cuenta el estado de salida es utilizar la sustitución de comando en una asignación simple : entonces el estado de salida de la asignación es el estado de salida de la última sustitución de comando en la (s) asignación (es) .
Tenga en cuenta que esto funcionará según lo previsto solo si hay una única sustitución de comando, ya que solo se tiene en cuenta el estado de la última sustitución. Por ejemplo, el siguiente comando es exitoso (de acuerdo con el estándar y en cada implementación que he visto):
a=$(false)$(echo foo)
Otro caso a tener en cuenta es subniveles explícitas : (somecommand)
. Según la interpretación anterior, el subshell puede devolver un estado distinto de cero, pero dado que este no es un comando simple en el shell principal, el shell principal debe continuar. De hecho, todos los proyectiles que conozco hacen que los padres regresen en este punto. Si bien esto es útil en muchos casos, como (cd /some/dir && somecommand)
cuando los paréntesis se usan para mantener una operación como un cambio de directorio actual local, viola la especificación si set -e
se desactiva en la subshell, o si la subshell devuelve un estado distinto de cero de una manera que no lo terminaría, como usar !
un comando verdadero. Por ejemplo, todas las cenizas, bash, pdksh, ksh93 y zsh salen sin mostrarse foo
en los siguientes ejemplos:
set -e; (set +e; false); echo "This should be displayed"
set -e; (! true); echo "This should be displayed"
¡Sin embargo, ningún comando simple ha fallado mientras set -e
estaba en vigor!
Un tercer caso problemático son los elementos en una tubería no trivial . En la práctica, todos los shells ignoran las fallas de los elementos de la tubería que no sean la última, y exhiben uno de los dos comportamientos con respecto al último elemento de la tubería:
- ATT ksh y zsh, que ejecutan el último elemento de la canalización en el shell principal, funcionan como de costumbre: si un comando simple falla en el último elemento de la canalización, el shell ejecuta ese comando, que resulta ser el shell principal, salidas
- Otros shells aproximan el comportamiento al salir si el último elemento de la tubería devuelve un estado distinto de cero.
Al igual que antes, apagar set -e
o usar una negación en el último elemento de la tubería hace que devuelva un estado distinto de cero de una manera que no debería terminar el shell; los shells que no sean ATT ksh y zsh saldrán.
La pipefail
opción de Bash hace que una tubería salga inmediatamente debajo set -e
si alguno de sus elementos devuelve un estado distinto de cero.
Tenga en cuenta que, como complicación adicional, bash se apaga set -e
en subcapas a menos que esté en modo POSIX ( set -o posix
o tenga POSIXLY_CORRECT
en el entorno cuando se inicia bash).
Todo esto muestra que la especificación POSIX desafortunadamente hace un mal trabajo al especificar la -e
opción. Afortunadamente, las conchas existentes son en su mayoría consistentes en su comportamiento.
set -e; (cd /nonexisting)
.(Respondiendo la mía porque he encontrado una solución) Una solución es siempre asignar esto a una variable intermedia. De esta forma se establece el código de retorno (
$?
).Entonces
Sin embargo, saldrá
1
(o en su lugar saldrá siset -e
está presente):Saldrá
0
después de una línea en blanco. El código de retorno del eco (u otro comando que se ejecutó con la salida de retroceso) reemplazará el código de retorno 0.Todavía estoy abierto a soluciones que no requieren la variable intermedia, pero esto me ayuda en parte.
fuente
Como el OP señaló en su propia respuesta, asignar la salida del subcomando a una variable resuelve el problema; el
$?
que queda indemne.Sin embargo, un caso extremo aún puede confundirlo con falsos negativos (es decir, el comando falla pero el error no aparece),
local
declaración de variable:local myvar=$(subcommand)
será siempre volver0
!bash(1)
señala esto:Aquí hay un caso de prueba simple:
La salida:
fuente
VAR=...
ylocal VAR=...
realmente me desconcertó!Como otros han dicho,
local
siempre devolverá 0. La solución es declarar la variable primero:Salida:
fuente
Para salir en una falla de sustitución de comando, puede establecer explícitamente
-e
en un subshell, como este:fuente
Punto interesante!
Nunca me he tropezado con eso, porque no soy amigo de
set -e
(en cambio, prefiero hacerlotrap ... ERR
), pero ya lo he probado:trap ... ERR
tampoco detecta errores dentro$(...)
(o los anticuados backticks).Creo que el problema es (como tan a menudo) que aquí se llama un subshell y
-e
explícitamente significa el shell actual .La otra solución que se me ocurrió en este momento sería usar read:
Este lanzamiento
ERR
y con-e
el caparazón se terminará. Único problema: esto funciona solo para comandos con una línea de salida (o canaliza a través de algo que une líneas).fuente