Si ejecuto este comando bash y prefijo la declaración, de modo que la variable fruit
deberí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 echo
comando 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 fruit
variable 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
fruit
ya 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
echo
comando 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
IFS
valor de la variable temporal aún no está disponible cuando el shelldivisión de palabras otrovariable$var
mientras está aquí, el valor de la variable temporalfruit
aú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 10
cuando 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, somevar
es solo una variable de shell, no tiene nada de especial. Caparazónexpansión de parámetros(ver LESS='+/Parameter Expansion' man bash
) ocurreantesel ls
ejecutable está realmente cargado ("ejecutado") y el ls
comando (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 somevar
antes del ls
comando, entonces el hecho de que somevar=myfile
aparecerá en elambientedel ls
proceso, pero eso no afectará nada porque el ls
comando 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 bc
es 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 -q
para 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 bc
funciona. 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 echo
comando. 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_ARGS
comocaparazónvariable:
$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$
Aquí puede ver que nuestro echo
comando puede ver el contenido, pero no forma parte del entorno, por bc
lo que bc
no puede hacer nada especial con él.
Por supuesto, si ponemos la variable en bc
la lista de argumentos, veríamos algo:
$ bc -q "$BC_ENV_ARGS"
25
$
Pero aquí es elcaparazónexpandiendo la variable, y luego file1
es 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 file1
se 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 bc
entorno de cuando ejecuta ese proceso, por lo que el valor de esta variable de entorno esheredadoy afecta cómo bc
funciona.
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 bc
comando 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...:
Las palabras que se reconocen como asignaciones variables [...] se guardan para su procesamiento en los pasos 3 y 4.
Se ampliarán las palabras que no sean asignaciones variables o redirecciones. [...]
Las redirecciones se realizarán como se describe en Redirección.
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...)