Las asignaciones son como comandos con un estado de salida, excepto cuando hay sustitución de comandos?

10

Consulte los siguientes ejemplos y sus resultados en shells POSIX:

  1. false;echo $?o false || echo 1:1
  2. false;foo="bar";echo $?o foo="bar" && echo 0:0
  3. foo=$(false);echo $?o foo=$(false) || echo 1:1
  4. foo=$(true);echo $?o foo=$(true) && echo 0:0

Como se menciona en la respuesta más votada en /programming/6834487/what-is-the-variable-in-shell-scripting :

$? se usa para encontrar el valor de retorno del último comando ejecutado.

Esto es probablemente un poco engañoso en este caso, así que obtengamos la definición POSIX que también se cita en una publicación de ese hilo:

? Se expande al estado de salida decimal de la canalización más reciente (consulte Canalizaciones).

Por lo tanto, parece que una asignación en sí misma cuenta como un comando (o más bien una parte de la tubería) con un valor de salida cero pero que se aplica antes del lado derecho de la asignación (por ejemplo, la sustitución del comando llama en mis ejemplos aquí).

Veo cómo este comportamiento tiene sentido desde un punto de vista práctico, pero me parece algo inusual que la tarea en sí misma cuente en ese orden. Tal vez para aclarar por qué me resulta extraño, supongamos que la asignación fue una función:

ASSIGNMENT( VARIABLE, VALUE )

entonces foo="bar"sería

ASSIGNMENT( "foo", "bar" )

y foo=$(false)sería algo como

ASSIGNMENT( "foo", EXECUTE( "false" ) )

lo que significa que se EXECUTEejecuta primero y solo después ASSIGNMENT se ejecuta, pero sigue siendo el EXECUTEestado lo que importa aquí.

¿Estoy correcto en mi evaluación o estoy malentendido / me falta algo? ¿Son esas las razones correctas para que vea este comportamiento como "extraño"?

phk
fuente
1
Lo siento, pero no me queda claro lo que encuentras extraño.
Kusalananda
1
@Kusalananda Tal vez sea útil decirte que comenzó conmigo preguntándome: "¿Por qué false;foo="bar";echo $?siempre devuelve 0 cuando el último comando real que se ejecutó fue false?" Básicamente, las tareas se comportan de manera especial cuando se trata de códigos de salida. Su código de salida siempre es 0, excepto cuando no se debe a algo que se ejecutó como parte del lado derecho de la tarea.
phk
1
Relacionado: stackoverflow.com/questions/20157938/…
Kusalananda

Respuestas:

10

El estado de salida de las tareas es extraño . La forma más obvia para que una asignación falle es si la variable objetivo está marcada readonly.

$ err(){ echo error ; return ${1:-1} ; }
$ PS1='$? $ '
0 $ err 42
error
42 $ A=$(err 12)
12 $ if A=$(err 9) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
E=9 ?=0
0 $ readonly A
0 $ if A=$(err 10) ; then echo wrong ; else E=$? ; echo "E=$E ?=$?" ; fi
A: is read only
1 $

Tenga en cuenta que ni las rutas verdaderas ni falsas de la declaración if se tomaron, la falla de la asignación detuvo la ejecución de la declaración completa. bash en modo POSIX y ksh93 y zsh abortarán un script si falla una asignación.

Para citar el estándar POSIX sobre esto :

Un comando sin un nombre de comando, pero uno que incluye una sustitución de comando, tiene un estado de salida de la última sustitución de comando que realizó el shell.

Esta es exactamente la parte de la gramática de shell involucrada en

 foo=$(err 42)

que proviene de un simple_command(simple_command → cmd_prefix → ASSIGNMENT_WORD). Entonces, si una asignación tiene éxito, el estado de salida es cero a menos que haya una sustitución de comando, en cuyo caso el estado de salida es el estado de la última. Si la asignación falla, el estado de salida no es cero, pero es posible que no pueda atraparlo.

Ícaro
fuente
1
Para agregar a su respuesta, aquí hay una respuesta de un hilo diferente donde se cita un nuevo estándar POSIX sobre esto, la conclusión es básicamente la misma: unix.stackexchange.com/a/270831/117599
phk
4

Tu dices,

... parece que una asignación en sí cuenta como un comando ... con un valor de salida cero, pero que se aplica antes del lado derecho de la asignación (por ejemplo, una llamada de sustitución de comando ...)

Esa no es una forma terrible de verlo. Pero es una ligera simplificación excesiva. El estado general de devolución de

A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
es el estado de salida de . La asignación que ocurre después de la asignación no establece el estado general de salida en 0.cmd4E=D=

Además, como señala icarus , las variables se pueden establecer como de solo lectura. Considere la siguiente variación en el ejemplo de icarus:

$ err() { echo "stdout $*"; echo "stderr $*" >&2; return ${1:-1}; }
$ readonly A
$ Z=$(err 41 zebra) A=$(err 42 antelope) B=$(err 43 badger)
stderr 41 zebra
stderr 42 antelope
bash: A: readonly variable
$ echo $?
1
$ printf "%s = %s\n" Z "$Z" A "$A" B "$B"
Z = stdout 41 zebra
A =
B =
$

Aunque Aes de solo lectura, bash ejecuta la sustitución del comando a la derecha de A=- y luego aborta el comando porque Aes de solo lectura. Esto contradice aún más su interpretación de que el valor de salida de la asignación se aplica antes del lado derecho de la asignación.

G-Man dice 'restablecer a Mónica'
fuente