¿Por qué es legal establecer una variable antes de un comando en bash?

68

Acabo de encontrar varias respuestas, como analizar un archivo de texto delimitado ... que usa la construcción:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

donde la IFSvariable se establece antes del readcomando.

He estado leyendo la referencia de bash pero no puedo entender por qué esto es legal.

Lo intenté

$ x="once upon" y="a time" echo $x $y

desde el símbolo del sistema bash pero no se hizo eco de nada. ¿Alguien puede señalarme dónde se define esa sintaxis en la referencia que permite que la variable IFS se establezca de esa manera? ¿Es un caso especial o puedo hacer algo similar con otras variables?

Mike Lippert
fuente
Consulte también ¿ Cuándo puedo usar un IFS temporal para la división de campos?
Gilles 'SO- deja de ser malvado'

Respuestas:

69

Está debajo de Shell Grammar, Comandos simples (énfasis agregado):

Un comando simple es una secuencia de asignaciones de variables opcionales seguidas de palabras y redirecciones separadas en blanco , y terminadas por un operador de control. La primera palabra especifica el comando que se ejecutará y se pasa como argumento cero. Las palabras restantes se pasan como argumentos al comando invocado.

Para que pueda pasar cualquier variable que desee. Su echoejemplo no funciona porque las variables se pasan al comando, no se establecen en el shell. El shell se expande $xy $y antes de invocar el comando. Esto funciona, por ejemplo:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
fuente
¡Gracias! Busqué mucho en Google antes de preguntar, y estaba tratando de averiguar dónde decía eso en la referencia, sin suerte. Estoy tratando de mejorar en la escritura de guiones bash y su respuesta ayuda.
Mike Lippert
1
Hmm, creo que necesito encontrar una mejor referencia, la mía (ver enlace arriba) no dice que no tenga una sección de Gramática de Shell.
Mike Lippert
1
@MikeLippert Consulte 3.7.4 en esa referencia ("El entorno para cualquier comando o función simple puede aumentarse temporalmente con el prefijo con asignaciones de parámetros"). Creo que esa referencia es de una versión anterior de bash. Acabo de ejecutar man bashen mi sistema ...
derobert
29

Las variables definidas se convierten en variables de entorno en el proceso bifurcado.

Si tu corres

A="b" echo $A

entonces golpear primero se expande $Aen ""y a continuación, ejecuta

A="b" echo

Aquí está la forma correcta:

x="once upon" y="a time" bash -c 'echo $x $y'

Observe las comillas simples bash -c, de lo contrario tiene el mismo problema que el anterior.

Entonces, su ejemplo de bucle es legal porque el comando bash 'read' incorporado buscará IFS en sus variables de entorno y buscará ,. Por lo tanto,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

imprimirá TEST is and I is test

Por último, en cuanto a la sintaxis, en un bucle for se espera una cadena. Por lo tanto, tuve que usar backticks para convertirlo en un comando. Sin embargo, los bucles while esperan una sintaxis de comando, como IFS=, read xx yy zz.

Mike Fairhurst
fuente
1
Gracias, aprendí un poco más de lo que pregunté por tu respuesta. También votaría su respuesta, pero todavía no se me permite y marqué la respuesta aceptada en la primera.
Mike Lippert
1
Gracias, eso es lo que esperaba. ¡Y un voto es menos significativo que escuchar tu aprecio!
Mike Fairhurst
Para aclarar su comentario debajo de la primera línea de código: Sí, con la Avariable no establecida bash se expande $Aa una cadena vacía, pero para evitar confusiones no usaría ""porque el código no es equivalente a A="b" echo "". No habrá argumento para echo.
pabouk
8

man bash

AMBIENTE

[...] El entorno para cualquier comando o función simple puede aumentarse temporalmente con el prefijo con asignaciones de parámetros, como se describió anteriormente en PARÁMETROS. Estas declaraciones de asignación afectan solo al entorno visto por ese comando.

Las variables se expanden antes de que tenga lugar la asignación de variables. Por la razón obvia, eso var=xtambién funcionaría a la inversa, pero var=$othervarno lo haría. Es decir, $xse necesita antes de que esté disponible. Pero ese no es el problema principal. El principal problema es que la línea de comandos solo puede ser modificada por el entorno de shell, pero la asignación no se convierte en parte del entorno de shell.

Combina características: desea un reemplazo de la línea de comandos pero coloca la definición de la variable en el entorno de comandos. Los reemplazos de la línea de comando deben ser realizados por el shell. El entorno debe ser explícitamente utilizado por el comando llamado. Si y cómo se hace esto depende del comando.

La ventaja de este uso es que puede configurar el entorno para un subproceso sin afectar el entorno del shell.

x="once upon" y="a time" bash -c 'echo $x $y'

funciona como se espera porque en ese caso ambas características se combinan: el reemplazo de la línea de comandos no se realiza mediante el shell de llamada sino mediante el shell de subproceso.

Hauke ​​Laging
fuente
1
Es un poco más sutil que eso, porque el ejemplo también funciona x="once upon" y="a time" eval 'echo $x $y'cuando no hay ningún subproceso involucrado, ya que evales un incorporado. Supongo que la cita relevante de la página de manual es The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Teniendo en cuenta el ejemplo de la pregunta, tiene que ser así, ya readque también es una función incorporada y funciona con un estado de cambio temporal IFS.
David Ongaro
4

El comando que proporcione es diferente porque $xy $yse expanden antes de las echocarreras de mando, por lo que sus valores de la corriente se utilizan cáscara, no los valores que echose ven en su entorno si fuera a buscar.

chepner
fuente
¿Cómo se puede expandir algo en la línea de comando después de que se ejecuta el comando ...?
Hauke ​​Laging
Las asignaciones previas al comando para xy yson para el entorno que se echoejecuta, no para el entorno en el que echose expanden los argumentos . Para IFS=, read xx yy zz, toda la cadena se lee, sin dividir, por el readcomando. Entonces , esa cadena se divide de acuerdo con el valor de IFS, con las piezas correspondientes asignados a xx, yy, y zz.
chepner
Solo quería señalar que la frase "expandido antes de que se ejecute el comando" no tiene mucho sentido porque después del inicio del comando ya nada se expande. Además: ¿no has mirado mi respuesta o crees que necesito una explicación de lo que está sucediendo ...?
Hauke ​​Laging
1
No afirmé que necesita una explicación ni que se pueda expandir nada después de que se ejecute el comando. Sin embargo, es cierto que bashprimero analiza la línea de comando dada, reconoce que hay dos asignaciones de variables para aplicar al entorno del comando que viene, identifica el comando a ejecutar ( echo), expande los parámetros encontrados en los argumentos, luego ejecuta el comando echocon los argumentos expandidos.
chepner
En el caso de echono estaba seguro si podía "ver" la variable, ya que es un comando incorporado y, por lo tanto, no se ejecuta en una subshell que podría tener su propio entorno. Pero lo probé con el evalque también está incorporado y de hecho lo sabe. Por ejemplo, pruebe lo a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aque muestra que asolo se establece dentro evalaunque el pid sea el mismo y el entorno no se altere durante la evaluación. (Para acceder /procnecesitas ejecutar esto bajo Linux.) Parece que bash hace algo de magia adicional aquí.
David Ongaro
2

Voy por la imagen más grande de " por qué es legal"

Respuesta: Para que pueda llamar o invocar un programa y para esa invocación solo use una variable con una variable en particular.

Como ejemplo: tiene un parámetro para una conexión de base de datos llamado 'db_connection', y normalmente pasa 'test' como nombre para su conexión de base de datos de prueba. De hecho, incluso puede establecerlo como un valor predeterminado que no necesita pasar explícitamente. A veces, sin embargo, desea trabajar con la base de datos ci. Entonces, pasa el parámetro como 'ci' y luego el programa que se llama utiliza ese parámetro de la base de datos como el nombre de la base de datos que se usará para todas las llamadas a la base de datos. Para la próxima ejecución, si no repite el enfoque y simplemente llama al programa, la variable volverá a su valor predeterminado anterior.

Michael Durrant
fuente
0

También puedes usar ;. Se evaluará antes porque es un separador de comandos.

x="once upon" y="a time"; echo $x $y
camabeh
fuente
1
Esto crea dos variables de shell y no crea variables de entorno en la utilidad dada. Esto es algo muy diferente. También existe el efecto secundario de que el shell actual ahora tiene nuevas variables.
Kusalananda