¿Hay alguna diferencia entre "." Y "fuente" en bash, después de todo?

38

Estaba buscando la diferencia entre el "." y los comandos incorporados "fuente" y algunas fuentes (p. ej., en esta discusión y la página de manual de bash ) sugieren que son exactamente lo mismo.

Sin embargo, después de un problema con las variables de entorno, realicé una prueba. Creé un archivo testenv.shque contiene:

#!/bin/bash
echo $MY_VAR

En el símbolo del sistema, realicé lo siguiente:

> chmod +x testenv.sh
> MY_VAR=12345
> ./testenv.sh

> source testenv.sh
12345
> MY_VAR=12345 ./testenv.sh
12345

[tenga en cuenta que el primer formulario devolvió una cadena vacía]

Por lo tanto, este pequeño experimento sugiere que hay es una diferencia después de todo, donde por el comando "fuente", el medio ambiente hijo hereda todas las variables de la matriz uno, donde por "" no es asi.

¿Me estoy perdiendo algo, o esta es una característica indocumentada / en desuso de bash ?

[GNU bash, versión 4.1.5 (1) -release (x86_64-pc-linux-gnu)]

ysap
fuente

Respuestas:

68

Respuesta corta

En su pregunta, el segundo comando no utiliza el .shell incorporado ni el sourceincorporado. En cambio, en realidad está ejecutando el script en un shell separado, invocando por nombre como lo haría con cualquier otro archivo ejecutable. Esto le da un conjunto separado de variables (aunque si exporta una variable en su shell principal, será una variable de entorno para cualquier proceso secundario y, por lo tanto , se incluirá en las variables de un shell secundario ). Si cambia el /a un espacio, eso lo ejecutaría con el .incorporado, que es equivalente a source.

Explicación Extendida

Esta es la sintaxis del sourceshell incorporado, que ejecuta el contenido de un script en el shell actual (y, por lo tanto, con las variables del shell actual):

source testenv.sh

Esta es la sintaxis del .incorporado, que hace lo mismo que source:

. testenv.sh

Sin embargo, esta sintaxis ejecuta el script como un archivo ejecutable, iniciando un nuevo shell para ejecutarlo:

./testenv.sh

Eso no está utilizando el .incorporado. Más bien, .es parte de la ruta al archivo que está ejecutando. En términos generales, puede ejecutar cualquier archivo ejecutable en un shell invocando con un nombre que contiene al menos un /carácter. Por lo tanto, ejecutar un archivo en el directorio actual, precediéndolo por, ./es la forma más fácil. A menos que el directorio actual esté en su PATH, no puede ejecutar el script con el comando testenv.sh. Esto es para evitar que las personas ejecuten accidentalmente archivos en el directorio actual cuando tengan la intención de ejecutar un comando del sistema o algún otro archivo que exista en algún directorio listado en la PATHvariable de entorno.

Como ejecutar un archivo por nombre (en lugar de con sourceo .) lo ejecuta en un nuevo shell, tendrá su propio conjunto de variables de shell. El nuevo shell hereda las variables de entorno del proceso de llamada (que en este caso es su shell interactivo) y esas variables de entorno se convierten en variables de shell en el nuevo shell. Sin embargo, para que una variable de shell se pase al nuevo shell, debe ser uno de los siguientes:

  1. La variable de shell se ha exportado, lo que hace que sea una variable de entorno. Use el exportshell incorporado para esto. En su ejemplo, puede usar export MY_VAR=12345para establecer y exportar la variable en un solo paso, o si ya está configurado, simplemente puede usar export MY_VAR.

  2. La variable de shell se establece y se pasa explícitamente para el comando que está ejecutando, lo que hace que sea una variable de entorno mientras dura el comando que se ejecuta. Esto generalmente logra que:

    MY_VAR=12345 ./testenv.sh

    Si se MY_VARtrata de una variable de shell que no se ha exportado, incluso puede ejecutarse testenv.shcon MY_VARpass como variable de entorno configurándola en sí misma :

    MY_VAR="$MY_VAR" ./testenv.sh

./ La sintaxis de los scripts requiere una línea Hashbang para funcionar (correctamente)

Por cierto, tenga en cuenta que, cuando invocar un archivo ejecutable por el nombre que el anterior (y no con el .o sourcebombardear muebles empotrados), lo cáscara programa se utiliza para ejecutarlo se no determina normalmente por lo shell se está ejecutando desde . En lugar:

  • Para archivos binarios, el kernel puede configurarse para ejecutar archivos de ese tipo en particular. Examina los dos primeros bytes del archivo en busca de un "número mágico" que indique qué tipo de ejecutable binario es. Así es como se pueden ejecutar los archivos binarios ejecutables.

    Esto es, por supuesto, extremadamente importante, porque un script no puede ejecutarse sin un intérprete u otro intérprete, ¡que es un binario ejecutable! Además, muchos comandos y aplicaciones son binarios compilados en lugar de scripts.

    ( #!es la representación de texto del "número mágico" que indica un ejecutable de texto).

  • Para los archivos que se supone que se ejecutan en un shell u otro lenguaje interpretado, la primera línea se ve así:

    #!/bin/sh

    /bin/shpuede ser reemplazado por cualquier otro intérprete o intérprete de comandos destinado a ejecutar el programa. Por ejemplo, un programa Python podría comenzar con la línea:

    #!/usr/bin/python

    Estas líneas se llaman hashbang, shebang y otros nombres similares. Vea esta entrada de FOLDOC , este artículo de Wikipedia y ¿El intérprete lee #! / Bin / sh? para más información.

  • Si un archivo de texto está marcado como ejecutable y lo ejecuta desde su shell (como ./filename) pero no comienza #!, el núcleo no puede ejecutarlo. Sin embargo, al ver que esto ha sucedido, su shell intentará ejecutarlo pasando su nombre a algún shell. Existen pocos requisitos sobre qué shell es ( "el shell ejecutará un comando equivalente a tener un shell invocado ..." ). En la práctica , algunos shells, incluido bash*, ejecutan otra instancia de sí mismos, mientras que otros usan/bin/sh. Le recomiendo que evite esto y use una línea hashbang en su lugar (o ejecute el script pasándolo al intérprete deseado, por ejemplo bash filename).

    * Manual de GNU Bash , 3.7.2 Búsqueda y ejecución de comandos : "Si esta ejecución falla porque el archivo no está en formato ejecutable y el archivo no es un directorio, se supone que es un script de shell y el shell lo ejecuta como se describe en scripts de Shell ".

Eliah Kagan
fuente
2
Lo que encontré útil sourcees que las funciones estuvieron disponibles desde bash sin necesidad de cargar o iniciar nuevamente. Ejemplo #!/bin/bash function olakease {echo olakease;}. Una vez que lo cargue, source file.shpuede llamar directamente olakeasedesde bash. Realmente me gusta eso. Source se ejecuta y luego carga muchas cosas, el punto .es solo para ejecución y es algo así como el usobash file.sh
m3nda
2
@ erm3nda también .tiene este comportamiento: . file.shy source file.shhace exactamente lo mismo, incluidas las funciones de retención definidas en file.sh. (Tal vez estás pensando ./file.sh, lo cual es diferente. Pero eso no usa el .incorporado; en cambio, .es parte del camino.)
Eliah Kagan
¡Oh !, no leí con atención el archivo. [Espacio]. Muchas gracias.
m3nda
13

Sí, te estás perdiendo algo.

Creo que estás confundiendo el '.' eso significa directorio actual, como en ./testenv.shy el '.' eso significa source(que es un comando incorporado). Entonces en el caso cuando '.' significa sourceque lo sería . ./testenv.sh. ¿Tener sentido?

Así que prueba esto:

MY_VAR=12345 
. ./testenv.sh
usuario1477
fuente
2
El ./le dice exactamente dónde está el archivo, sin él, bash buscará primero a través de PATH, luego pruebe el directorio actual si no lo encontró. Si bash se ejecuta en modo POSIX, y no proporciona una ruta al archivo (como ./), solo buscará en PATH y no podrá encontrar el archivo si el directorio actual no está en PATH.
geirha
@geirha Sí, tienes razón, source(y .) en realidad comprobará $ PATH primero, a pesar de que realmente no están ejecutando el script en el sentido habitual. Mi comentario (anterior) era incorrecto.
Eliah Kagan
Corto y al punto +1
David Morales