¿Por qué "A = 10 echo $ A" no imprime 10?

25

Este comando:

A=10 echo $A

Imprime una línea vacía. ¿Por qué no 10? ¿Por qué no funciona la configuración del entorno temporal en el lugar?

Quiero saber la razón y la explicación en lugar de la solución.

solía

LANG=C gcc ...

para forzar gcc, use el idioma de reserva (inglés) en lugar del idioma del sistema (chino). Así que supongo que un VAR=valueprefijo configurará un entorno temporal para el comando que lo sigue. Pero parece que tengo algunos malentendidos.

Motor de tierra
fuente

Respuestas:

22

Es una cuestión del orden en que ocurren los diferentes pasos para evaluar un comando.

A=10 echo $Aprimero analiza el comando en un comando simple compuesto de tres palabras A=10, echoy $A. Luego, cada palabra se somete a una sustitución de variables, es decir, la transformación de expansiones variables, como $Aen sus valores (estoy omitiendo pasos que no hacen nada visible).

Si Atiene el valor fooinicialmente, el resultado de las medidas de expansión es un simple comando que todavía tiene tres palabras: A=10, echoy foo. (El shell también recuerda en este punto qué caracteres estaban inicialmente entre comillas, en este caso, ninguno). El siguiente paso es ejecutar el comando. Como A=10comienza con un nombre de variable válido seguido de un signo igual, se trata como una asignación; la variable Ase establece 10tanto en el shell como en el entorno durante la ejecución del comando. (Normalmente, debe escribir export Apara tener Aen el entorno y no solo como una variable de shell; esta es una excepción). La siguiente palabra no es una asignación, por lo que se trata como un nombre de comando (es un comando incorporado). losechoEl comando no depende de ninguna variable, por lo que A=10 echo $Atiene exactamente el mismo efecto que echo $A.

Si desea establecer una variable solo para la duración de un comando, pero teniendo en cuenta la asignación al ejecutar el comando, puede usar una subshell. Una subshell, indicada entre paréntesis, hace que todos los cambios de estado (asignaciones de variables, directorio actual, definiciones de funciones, etc.) sean locales a la subshell.

(A=10; echo $A)

Haga eso export A=10si desea exportar la variable al entorno para que sea vista por programas externos.

Gilles 'SO- deja de ser malvado'
fuente
Gracias, ¿puedo decir A=10 (echo $A)y obtener 10?
Earth Engine
2
@EarthEngine No, eso sería un error de sintaxis. La asignación debe estar al comienzo de un comando simple (es decir, solo un nombre de comando y algunos parámetros, y opcionalmente algunas asignaciones iniciales y algunas redirecciones). A=10; (echo $A)salidas, 10pero también establece Apara el resto del script.
Gilles 'SO- deja de ser malvado'
2
@EarthEngine Pero puedes decir A=10 eval 'echo $A'. Las comillas simples dejan de $Ainterpretarse hasta que se evalúa toda la línea ... En ese momento A = 10. Considero que esta respuesta es más correcta que la aceptada.
Oli
Creo que esta es la explicación correcta. La razón del comportamiento es solo el orden de cuándo está ocurriendo la expansión $Ay la asignación de A. Por ejemplo, A=5; A=6 let 'a=A'; echo $adevuelve 6no 5y no creo que letinicie una subshell, ya que es un comando incorporado.
David Ongaro
@EarthEngine: Cuando se dice que la explicación correcta es el fin de la evaluación, puede ser engañosa: A=10 echo $Ase no establecido A=10para todos los comandos después, incluso si están en diferentes líneas (cuando sea claramente la asignación fue ya evaluada). No se trata de orden, se trata de alcance
MestreLion
37

Cuando se utiliza LANG=C gcc ... lo que sucede es que la cáscara establece LANG para el gccmedio ambiente 's solamente , y no para el actual entorno de sí mismo ( ver nota ). Entonces, después de que gccfinaliza, LANGvuelve a su valor anterior (o no establecido).

Además, cuando lo usa A=10 echo $Aes el shell que reemplaza $ A, no echo, y esta sustitución (llamada "expansión") ocurre antes de que se evalúe la instrucción (incluida la asignación), por lo que para que funcione como Ael valor esperado ya debe estar establecido en el entorno actual anterior a esa declaración.

Es por eso A=10 echo $Aque no funciona como se esperaba: A=10se establecerá para echo, pero echo ignora internamente el valor de la variable de entorno A. Y $Ase reemplaza con el valor establecido en el shell actual (que no es ninguno), y luego se pasa como un argumento para hacer eco.

Por lo que su suposición es correcta: VAR=value command hace el trabajo, pero esto sólo es relevante si commandinternamente utiliza VAR. De lo contrario, puede pasar valuecomo argumento a command, pero los argumentos se reemplazan por el shell actual , por lo que deben establecerse antes del uso:VAR=value; command "$VAR"

Si sabe cómo crear un script ejecutable, puede intentarlo como prueba:

#!/bin/sh
echo "1st argument is $1"
echo "A is $A"

Guárdalo como testscripty prueba:

$ A=5; A=10 testscript "$A"; echo "$A"
1st argument is 5
A is 10
5

Por último, pero no menos importante, vale la pena conocer la diferencia entre las variables de entorno y shell y los argumentos del programa .

Aquí hay algunas buenas referencias:

.

(*) Nota: técnicamente, el shell también se establece en el entorno actual, y aquí está el por qué: algunos comandos, como echo, ready testestán integrados en el shell , y como tales no generan un proceso hijo. Se ejecutan en el entorno actual. Pero el shell se encarga de que la asignación solo dure hasta que se ejecute el comando, por lo que, a todos los efectos prácticos, el efecto es el mismo: la asignación solo es vista por ese comando único.

MestreLion
fuente
2
Esta explicación es realmente incorrecta, aunque da como resultado la conclusión correcta en todos los casos, salvo en algunos casos. La explicación real es el orden de expansión: $Ase evalúa antes de que se realice la asignación. Creo que su explicación solo falla en el caso de las utilidades integradas regulares cuyo comportamiento depende del valor de la variable: la función incorporada sí ve el valor asignado. Un ejemplo común es IFS=: read one two three rest, que lee campos separados por dos puntos: el valor readincorporado sí ve el valor de IFS.
Gilles 'SO- deja de ser malvado'
"No para el shell actual en sí" está mal: la variable se establece en el shell actual, pero solo dura el comando simple actual. echovería el valor 10de A, si le importara.
Gilles 'SO- deja de ser malvado'
@Gilles: muchas gracias por la aclaración! No estaba al tanto de esta sutileza. Por lo tanto, si entendí correctamente, bash tiene que establecerse para el entorno actual; de lo contrario, los componentes integrados (que no generan uno nuevo pid) no verían la asignación como lo harían otros comandos "regurlar". Pero se desarma después de que se hace el comando para limitar el alcance de la asignación. Si esto es correcto, arreglaré mi respuesta en consecuencia. PD: aparte de los tecnicismos, sigo pensando que una respuesta debería enfatizar el aspecto del alcance, no el orden de evaluación, de lo contrario uno podría pensar que A=10 test; echo $Ase imprimirá 10
MestreLion
3

Una forma posiblemente limpia de hacer lo que aparentemente desea es emitir el comando:

A=10 eval 'echo $A'

Lo que en efecto diferirá la sustitución del valor 10 en el lugar de $ A a un contexto posterior (es decir, 'dentro' de la evaluación, que ya conoce la asignación). Tenga en cuenta que las comillas simples son esenciales. Tal construcción comunica limpiamente la asignación a su comando deseado (eco en este caso) sin correr el riesgo de contaminar su entorno actual.

nhokka
fuente