¿Por qué bash no expande esta variable cuando prefijo un comando con una "asignación de variable única"

¿Por qué bash no expande esta variable cuando prefijo un comando con una "asignación de variable única"

Si ejecuto este comando bash y prefijo la declaración, de modo que la variable fruitdebería existir, pero solo mientras dure este comando:

$ fruit=apple echo $fruit

$

El resultado es una línea vacía. ¿por qué?

Para citar un comentario de comodín enesta pregunta:

la expansión de parámetros la realiza el shell y la variable "fruta" no es una variable del shell; es sólo una variable de entorno dentro del entorno del comando "echo"

Una variable de entorno sigue siendo una variable, por lo que seguramente todavía debería estar disponible para el comando echo.

Respuesta1

El problema es que el shell actual está expandiendo la variable demasiado pronto; no está establecido en su contexto, por lo que el echocomando no recibe ningún argumento, es decir, el comando termina siendo:

$ fruit=apple echo

Aquí hay una solución donde la variable no se expande demasiado pronto debido a las comillas simples:

$ fruit=apple sh -c 'echo $fruit'

Alternativamente, también puede utilizar un script de shell de una línea que demuestre que la fruitvariable se pasa correctamente al comando ejecutado:

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

Algunos comentarios ya que esta pregunta ha provocado controversias y discusiones inesperadas:

  • El hecho de que la variable fruitya esté exportada o no no afecta el comportamiento, lo que importa es cuál es el valor de la variable en el momento preciso en que el shell la expande.
$ exportar fruta=plátano
$ fruta=manzana eco $fruta
banana
  • El hecho de que el echocomando sea integrado no afecta el problema del OP. Sin embargo, hay casos en los que el uso de funciones integradas o de shell con esta sintaxis tiene efectos secundarios inesperados, por ejemplo:
$ exportar fruta=plátano
$ fruta=eval manzana 'echo $fruta'
manzana
$ eco $ fruta
manzana
  • Si bien existe una similitud entrela pregunta hecha aquíy ese, este no es exactamente el mismo tema. Con esa otra pregunta, el IFSvalor de la variable temporal aún no está disponible cuando el shelldivisión de palabras otrovariable $varmientras está aquí, el valor de la variable temporal fruitaún no está disponible cuando el shellse expandeelmismovariable.

  • También hayesa otra preguntadonde el OP pregunta sobre la importancia de la sintaxis utilizada y, más precisamente, pregunta "¿por qué funciona esto?". En este caso, el OP es consciente del significado, pero informa de un comportamiento inesperado y pregunta sobre su causa, es decir, "¿por qué no funciona?". Ok, después de leer más de cerca la mala captura de pantalla publicada en la otra pregunta, la misma situación se describe allí ( BAZ=jake echo $BAZ), así que sí, después de todo, esto es un duplicado.

Respuesta2

Para entender esto correctamente, primero diferenciemosvariables de shelldeVariables de entorno.

Las variables de entorno son una propiedad que TODOS los procesos tienen, ya sea que las utilicen internamente o no. Incluso sleep 10cuando se está ejecutando tiene variables de entorno. Así como todos los procesos tienen un PID (identificador de proceso), un directorio de trabajo actual (cwd), un PPID (PID principal), una lista de argumentos (incluso si está vacía), etc. Todos los procesos también tienen lo que se llama un "entorno", que se hereda del proceso padre cuando se bifurca.

Desde la perspectiva del autor de una utilidad (alguien que escribe código en C), los procesos tienen la capacidad de establecer, desarmar o cambiar variables de entorno. Sin embargo, desde la perspectiva del autor de guiones, la mayoría de las herramientas no ofrecen esa posibilidad a sus usuarios. En lugar de eso, usas tucaparazónpara alterar el entorno del proceso que luego se hereda cuando se ejecuta el comando (binario externo) que llamó. (El entorno de su shell en sí puede modificarse y la modificación heredarse, o puede indicarle a su shell que realice la modificación después de la bifurcación pero antes de ejecutar el comando que llamó. De cualquier manera, el entorno se hereda. Veremos ambos enfoques.)

Las variables de Shell son otra cosa. A pesar dedentro del caparazónse comportan de la misma manera, la diferencia es que las meras "variables de shell" no alteran ni influyen en el comportamiento de los comandos que llamasdetu caparazón. En la terminología adecuada, la distinción en realidad se expresaría de manera un poco diferente;exportadoLas variables de Shell se convertirán en parte del entorno de las herramientas que usted llame, mientras que las variables de Shell que sonnoexportado no lo hará. Sin embargo, encuentro más útil para la comunicación hacer referencia a variables de shell que no se exportan como "variables de shell" y variables de shell quesonexportados como "variables ambientales" porquesonvariables de entorno desde la perspectiva de procesos bifurcados desde el shell.


Eso es mucho texto. Veamos algunos ejemplos y describamos lo que está sucediendo:

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

En este ejemplo, somevares solo una variable de shell, no tiene nada de especial. Caparazónexpansión de parámetros(ver LESS='+/Parameter Expansion' man bash) ocurreantesel lsejecutable está realmente cargado ("ejecutado") y el lscomando (proceso) ni siquieravela cadena "signo de dólar somevar". Solo ve la cadena "miarchivo", la interpreta como la ruta a un archivo en el directorio de trabajo actual y obtiene e imprime información sobre él.

Si ejecutamos export somevarantes del lscomando, entonces el hecho de que somevar=myfileaparecerá en elambientedel lsproceso, pero eso no afectará nada porque el lscomando no hará nada con esta variable. para ver unefectode una variable de entorno, tenemos que elegir una variable de entorno que el proceso que estamos llamando realmente verificará y con la que hará algo.


antes de Cristo: Calculadora básica

Puede que haya un ejemplo mejor, pero se me ocurrió éste y no es demasiado complicado. Primero debes saber que bces una calculadora básica, que procesa y calcula expresiones matemáticas. (Después de procesar el contenido de cualquier archivo de entrada, procesa su entrada estándar. No voy a usar su entrada estándar para mis ejemplos; simplemente presionaré Ctrl-D, que no se mostrará en los fragmentos de texto a continuación. Además Estoy usando -qpara suprimir un mensaje introductorio en cada invocación).

La variable de entorno que ilustraré se describe en man bc:

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

Aquí va:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

Esto es sólo para mostrar cómo bcfunciona. En cada uno de estos casos tuve que presionar Ctrl-D para indicar "fin de entrada" a bc.

Ahora, pasemos una variable de entorno.directamentea bc:

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

Note que lo que ponemos en esa variable esnovisible más tarde desde un echocomando. Al colocar la asignación como parte del mismo comando (sin punto y coma tampoco), colocamos esa asignación de variable comopartedel entorno de bc—no afectó el propio shell que estamos ejecutando.

Ahora establezcamos BC_ENV_ARGScomocaparazónvariable:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

Aquí puede ver que nuestro echocomando puede ver el contenido, pero no forma parte del entorno, por bclo que bcno puede hacer nada especial con él.

Por supuesto, si ponemos la variable en bcla lista de argumentos, veríamos algo:

$ bc -q "$BC_ENV_ARGS"
25
$ 

Pero aquí es elcaparazónexpandiendo la variable, y luego file1es lo que realmente aparece en bc'slista de argumentos. Entonces esto todavía lo usa como una variable de shell, no como una variable de entorno.

Ahora "exportemos" esta variable para que sea a la vez una variable de shell Y una variable de entorno:

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

Y aquí puede ver que file1se procesó antes file2, aunque no se menciona aquí en la línea de comando. Es parte del entorno del shell y se convierte en parte del bcentorno de cuando ejecuta ese proceso, por lo que el valor de esta variable de entorno esheredadoy afecta cómo bcfunciona.

Todavía podemos anular esto por comando, incluso anularlo a un valor vacío:

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

Pero como puede ver, la variable permanece configurada y exportada en nuestro shell, visible tanto para el propio shell como para cualquier bccomando posterior quenoanular el valor. Permanecerá así a menos que lo "desexportemos" o lo "desconectemos". Haré esto último:

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

Otro ejemplo que implica generar otro caparazón:

Escriba los siguientes comandos uno tras otro en su shell y considere los resultados. Vea si puede predecir los resultados antes de ejecutarlos.

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

Y una última pregunta para mayor complejidad: ¿puedes predecir el resultado de los siguientes comandos ejecutados en secuencia? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

Por favor mencione en los comentarios cualquier aclaración deseada; Estoy seguro de que a esto le vendrían bien algunos. Pero es de esperar que sea útil incluso tal como está.

Respuesta3

Porque las expansiones en la línea de comando tienen lugar antes de las asignaciones de variables.La norma lo dice:

Cuando se requiere ejecutar un comando simple determinado, se debe realizar lo siguiente...:

  1. Las palabras que se reconocen como asignaciones variables [...] se guardan para su procesamiento en los pasos 3 y 4.

  2. Se ampliarán las palabras que no sean asignaciones variables o redirecciones. [...]

  3. Las redirecciones se realizarán como se describe en Redirección.

  4. Cada asignación de variable deberá ampliarse [...] antes de asignar el valor.

Tenga en cuenta el orden: en el primer paso, las tareas son sólosalvado, luego las otras palabras se expanden y solo al final se realizan asignaciones de variables.

Por supuesto, es muy probable que el estándar lo diga sólo porque siempre ha sido así y simplemente codificaron el comportamiento existente. No dice nada sobre la historia o el razonamiento detrás de esto. El shell tiene que identificar palabras que parecen asignaciones en algún momento (aunque solo sea para no tomarlas como parte del comando), por lo que supongo que podría funcionar en el otro orden, asignar variables primero y luego expandir cualquier cosa en la línea de comando. . (o simplemente hazlo de izquierda a derecha...)

información relacionada