¿Por qué no puedo especificar una variable de entorno y repetirla en la misma línea de comando?

91

Considere este fragmento:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Aquí me he puesto $SOMEVARa AAAen la primera línea - y cuando me hago eco de que en la segunda línea, consigo los AAAcontenidos como se esperaba.

Pero luego, si trato de especificar la variable en la misma línea de comando que echo:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... No obtengo BBBlo que esperaba, obtengo el valor anterior ( AAA).

¿Es así como se supone que deben ser las cosas? Si es así, ¿cómo es que puede especificar variables como LD_PRELOAD=/... program args ...y hacer que funcione? ¿Qué me estoy perdiendo?

sdaau
fuente
2
Funciona cuando hace que la asignación sea una declaración separada o cuando se invoca un script con su propio entorno, pero no cuando se antepone un comando en el entorno actual. ¡Interesante!
Todd A. Jacobs
1
La razón por la LD_PRELOADque funciona es que la variable se establece en el programa de medio ambiente - no en su línea de comandos.
Pausado hasta nuevo aviso.

Respuestas:

103

Lo que ves es el comportamiento esperado. El problema es que el shell padre se evalúa $SOMEVARen la línea de comandos antes de invocar el comando con el entorno modificado. Debe obtener la evaluación de $SOMEVARdiferido hasta que se establezca el entorno.

Sus opciones inmediatas incluyen:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Ambos usan comillas simples para evitar que el shell padre evalúe $SOMEVAR; solo se evalúa después de que se establece en el entorno (temporalmente, mientras dure el comando único).

Otra opción es usar la notación de sub-shell (como también sugirió Marcus Kuhn en su respuesta ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

La variable se establece solo en el sub-shell

Jonathan Leffler
fuente
Impresionante, @JonathanLeffler, muchas gracias por la explicación; ¡salud!
sdaau
La adición de @ markus-kuhn es difícil de sobreestimar.
Alex Che
37

El problema, revisado

Francamente, el manual es confuso en este punto. El manual de GNU Bash dice:

El entorno para cualquier comando o función simple [tenga en cuenta que esto excluye los elementos internos] puede aumentarse temporalmente con un prefijo con asignaciones de parámetros, como se describe en Parámetros de Shell. Estas declaraciones de asignación afectan solo al entorno visto por ese comando.

Si realmente analiza la oración, lo que está diciendo es que se modifica el entorno para el comando / función, pero no el entorno para el proceso padre. Entonces, esto funcionará:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

porque el entorno para el comando env se ha modificado antes de su ejecución. Sin embargo, esto no funcionará:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

debido a cuando el shell realiza la expansión de parámetros.

Pasos del intérprete

Otra parte del problema es que Bash define estos pasos para su intérprete:

  1. Lee su entrada desde un archivo (consulte Scripts de Shell), desde una cadena proporcionada como argumento a la opción de invocación -c (consulte Invocación de Bash) o desde la terminal del usuario.
  2. Divide la entrada en palabras y operadores, obedeciendo las reglas de cotización descritas en Cotización. Estos tokens están separados por metacaracteres. La expansión de alias se realiza mediante este paso (ver Alias).
  3. Analiza los tokens en comandos simples y compuestos (consulte Comandos de Shell).
  4. Realiza las diversas expansiones de shell (ver Expansiones de Shell), dividiendo los tokens expandidos en listas de nombres de archivo (ver Expansión de nombre de archivo) y comandos y argumentos.
  5. Realiza las redirecciones necesarias (consulte Redirecciones) y elimina los operadores de redirección y sus operandos de la lista de argumentos.
  6. Ejecuta el comando (consulte Ejecución de comandos).
  7. Opcionalmente, espera a que se complete el comando y recopila su estado de salida (consulte Estado de salida).

Lo que está sucediendo aquí es que las incorporaciones no obtienen su propio entorno de ejecución, por lo que nunca ven el entorno modificado. Además, los comandos simples (por ejemplo, / bin / echo) no conseguir una ennvironment modificado (por lo que el ejemplo env trabajó) pero la expansión shell está teniendo lugar en el actual entorno en el paso # 4.

En otras palabras, no está pasando 'aaa $ TESTVAR ccc' a / bin / echo; está pasando la cadena interpolada (como se expandió en el entorno actual) a / bin / echo. En este caso, dado que el entorno actual no tiene TESTVAR , simplemente está pasando 'aaa ccc' al comando.

Resumen

La documentación podría ser mucho más clara. ¡Menos mal que hay Stack Overflow!

Ver también

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

Todd A. Jacobs
fuente
Ya había votado a favor de esto, pero acabo de volver a esta pregunta, y esta publicación contiene exactamente las sugerencias que necesito; muchas gracias, @CodeGnome!
sdaau
No sé si Bash ha cambiado en esta área desde esta respuesta fue publicada, pero las asignaciones de variables prefijadas hago trabajo con órdenes internas ahora. Por ejemplo, FOO=foo eval 'echo $FOO'imprime foocomo se esperaba. Esto significa que puedes hacer cosas como IFS="..." read ....
Will Vousden
Creo que lo que está sucediendo es que Bash modifica su propio entorno temporalmente y lo restaura una vez que se ha completado el comando, lo que puede tener efectos secundarios extraños.
Will Vousden
22

Para lograr lo que quieres, usa

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Razón:

  • Debe separar la asignación por punto y coma o una nueva línea del siguiente comando; de lo contrario, no se ejecutará antes de que ocurra la expansión de parámetros para el siguiente comando (eco).

  • Debe realizar la asignación dentro de un entorno de subshell , para asegurarse de que no persista más allá de la línea actual.

Esta solución es más corta, más ordenada y más eficiente que algunas de las otras sugeridas, en particular, no crea un nuevo proceso.

Markus Kuhn
fuente
3
Para los futuros usuarios de Google que acaben aquí: esta es probablemente la mejor respuesta a esta pregunta. Para complicarlo aún más, si necesita que la asignación esté disponible en el entorno del comando, debe exportarla. La subcapa aún evita que la asignación persista. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
Eaj
@eaj Para exportar una variable de shell a una sola llamada de programa externo, como en su ejemplo, simplemente useSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn
10

La razón es que esto establece una variable de entorno para una línea. Pero echono hace la expansión, bashsí. Por lo tanto, su variable se expande realmente antes de que se ejecute el comando, aunque SOME_VAResté BBBen el contexto del comando echo.

Para ver el efecto, puede hacer algo como:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Aquí la variable no se expande hasta que se ejecuta el proceso hijo, por lo que verá el valor actualizado. si SOME_VARIABLEvuelve a comprobar en el shell principal, sigue siendo AAA, como se esperaba.

Error fatal
fuente
1
+1 para una explicación correcta de por qué no funciona como está escrito y para una solución alternativa viable.
Jonathan Leffler
1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Utilizar un ; para separar declaraciones que están en la misma línea.

Kyros
fuente
1
Eso funciona, pero no es el punto. La idea es configurar el entorno para un solo comando, no permanentemente como lo hace su solución.
Jonathan Leffler
Gracias por eso @Kyros; No sé cómo es que me lo perdí a estas alturas :) Aún divagando cómo LD_PRELOADy eso puede funcionar frente a un ejecutable sin punto y coma, aunque ... Muchas gracias de nuevo - ¡salud!
sdaau
@JonathanLeffler - de hecho, esa era la idea; No me di cuenta de que el punto y coma hace que el cambio sea permanente, ¡gracias por señalarlo!
sdaau
1

Aquí hay una alternativa:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
Brian
fuente
Ya sea que use &&o ;separe los comandos, la asignación persiste, lo que no es el comportamiento deseado de OP. Markus Kuhn tiene la versión correcta de esta respuesta.
Eaj