Y at-il une différence entre "." Et "source" dans bash, après tout?

38

Je cherchais la différence entre le "." et les commandes intégrées "source" et quelques sources (par exemple, dans cette discussion et la page de manuel bash ) suggèrent qu'elles sont identiques.

Cependant, suite à un problème avec les variables d'environnement, j'ai effectué un test. J'ai créé un fichier testenv.shqui contient:

#!/bin/bash
echo $MY_VAR

Dans l'invite de commande, j'ai effectué les opérations suivantes:

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

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

[notez que la 1ère forme a retourné une chaîne vide]

Ainsi, cette petite expérience suggère qu'il y a une différence après tout, où pour la commande « source », l'environnement enfant hérite de toutes les variables de la société mère un, où le « » ce ne est pas.

Est-ce que je manque quelque chose, ou est-ce une fonctionnalité non documentée / obsolète de bash ?

[GNU bash, version 4.1.5 (1) - release (x86_64-pc-linux-gnu)]

ysap
la source

Réponses:

68

Réponse courte

Dans votre question, la deuxième commande n'utilise ni le .shell intégré, ni le sourcescript intégré. Au lieu de cela, vous exécutez le script dans un shell séparé, en l'appelant par son nom, comme vous le feriez avec tout autre fichier exécutable. Cela lui donne un ensemble de variables séparé (cependant, si vous exportez une variable dans son shell parent, ce sera une variable d'environnement pour tout processus enfant et sera donc incluse dans les variables d'un shell enfant ). Si vous modifiez le /en un espace, cela le ferait avec le .paramètre prédéfini, ce qui équivaut à source.

Explication étendue

Il s'agit de la syntaxe du sourceshell intégré, qui exécute le contenu d'un script dans le shell actuel (et donc avec les variables du shell actuel):

source testenv.sh

C'est la syntaxe de la commande .intégrée, qui fait la même chose que source:

. testenv.sh

Cependant, cette syntaxe exécute le script en tant que fichier exécutable, en lançant un nouveau shell pour l'exécuter:

./testenv.sh

Ce n'est pas en utilisant le .intégré. Cela .fait plutôt partie du chemin du fichier que vous exécutez. En règle générale, vous pouvez exécuter n’importe quel fichier exécutable dans un shell en l’appelant avec un nom contenant au moins un /caractère. Pour exécuter un fichier dans le répertoire actuel, ./le plus facilement est de le précéder . À moins que le répertoire en cours ne se trouve dans votre PATH, vous ne pouvez pas exécuter le script avec la commande testenv.sh. Cela empêche les personnes d'exécuter accidentellement des fichiers du répertoire en cours lorsqu'elles ont l'intention d'exécuter une commande système ou un autre fichier existant dans un répertoire répertorié dans la PATHvariable d'environnement.

Étant donné que l’exécution d’un fichier par son nom (plutôt que avec sourceou .) l’exécute dans un nouveau shell, celui-ci aura son propre ensemble de variables. Le nouveau shell hérite des variables d'environnement du processus appelant (qui est dans ce cas votre shell interactif) et ces variables d'environnement deviennent des variables shell dans le nouveau shell. Cependant, pour qu'une variable shell soit transmise au nouveau shell, l'un des éléments suivants doit être le cas:

  1. La variable shell a été exportée, ce qui en fait une variable d’environnement. Utilisez le exportshell intégré pour cela. Dans votre exemple, vous pouvez utiliser export MY_VAR=12345pour définir et exporter la variable en une étape, ou si elle est déjà définie, vous pouvez simplement l'utiliser export MY_VAR.

  2. La variable shell est explicitement définie et transmise pour la commande que vous exécutez, ce qui en fait une variable d'environnement pour la durée de la commande en cours d'exécution. Cela accomplit généralement que:

    MY_VAR=12345 ./testenv.sh

    Si MY_VARest une variable shell qui n'a pas été exportée, vous pouvez même exécuter testenv.shavec MY_VARpassé en tant que variable d'environnement en la définissant elle-même :

    MY_VAR="$MY_VAR" ./testenv.sh

./ La syntaxe pour les scripts nécessite une ligne de hachage pour fonctionner (correctement)

Soit dit en passant, s'il vous plaît noter que, lorsque vous invoquez un exécutable par son nom comme ci - dessus (et non pas avec la .ou du sourceshell-ins), ce programme shell est utilisé pour exécuter est pas généralement déterminé par ce shell que vous lancez depuis . Au lieu:

  • Pour les fichiers binaires, le noyau peut être configuré pour exécuter des fichiers de ce type particulier. Il examine les deux premiers octets du fichier pour un "nombre magique" indiquant quel type d'exécutable binaire il s'agit. Voici comment les fichiers binaires exécutables peuvent être exécutés.

    Ceci est, bien sûr, extrêmement important, car un script ne peut pas s'exécuter sans un interpréteur ou un interpréteur, qui est un binaire exécutable! De plus, de nombreuses commandes et applications sont des fichiers binaires compilés plutôt que des scripts.

    ( #!est la représentation textuelle du "nombre magique" indiquant un texte exécutable.)

  • Pour les fichiers supposés être exécutés dans un shell ou dans un autre langage interprété, la première ligne est la suivante:

    #!/bin/sh

    /bin/shpeut être remplacé par tout autre shell ou interpréteur destiné à exécuter le programme. Par exemple, un programme Python peut commencer par la ligne suivante:

    #!/usr/bin/python

    Ces lignes s'appellent hashbang, shebang et un certain nombre d'autres noms similaires. Voir cette entrée FOLDOC , cet article de Wikipedia et l'interprète lit-il #! / Bin / sh? pour plus d'informations.

  • Si un fichier texte est marqué comme exécutable et que vous l'exécutez à partir de votre shell (comme ./filename) mais qu'il ne commence pas par #!, le noyau ne parvient pas à l'exécuter. Cependant, voyant que cela est arrivé, votre shell essayera de l'exécuter en transmettant son nom à un shell. Il y a peu d' exigences imposées à ce shell qui est ( « la coquille doit exécuter une commande équivalent à avoir un shell invoqué ... » ). En pratique , certains obus - y compris bash* - exécutent une autre instance d’eux-mêmes, tandis que d’autres utilisent/bin/sh. Je vous recommande vivement d'éviter cela et d'utiliser plutôt une ligne hashbang (ou exécutez le script en le transmettant à l'interpréteur souhaité, par exemple bash filename).

    * Manuel GNU Bash , 3.7.2 Recherche et exécution de commandes : "Si cette exécution échoue parce que le fichier n’est pas au format exécutable et que ce fichier n’est pas un répertoire, il est supposé être un script shell et le shell l’exécute comme décrit. dans les scripts shell ".

Eliah Kagan
la source
2
Ce que j’ai trouvé utile, sourcec’est que les fonctions sont devenues disponibles à partir de bash sans avoir besoin de charger ou de relancer. Exemple #!/bin/bash function olakease {echo olakease;}. Une fois que vous l'avez chargé, source file.shvous pouvez appeler directement olakeasedepuis bash. J'aime beaucoup ça. La source exécute puis charge beaucoup de choses, le point .est uniquement pour l'exécution et ressemble à une utilisationbash file.sh
m3nda
2
@ erm3nda .a également ce comportement: . file.shet source file.shfait exactement la même chose, y compris en conservant les fonctions définies dans file.sh. (Peut-être pensez-vous à ./file.shce qui est différent. Mais cela n'utilise pas la fonction .intégrée; cela .fait plutôt partie du chemin.)
Eliah Kagan
Oh !, je n'ai pas lu attentivement le fichier. Merci beaucoup.
m3nda
13

Oui, vous manquez quelque chose.

Je pense que vous confondez le '.' cela signifie répertoire en cours, comme dans ./testenv.shet le '.' cela signifie source(qui est une commande intégrée). Donc dans le cas où '.' signifie que sourcece serait . ./testenv.sh. Avoir un sens?

Alors essayez ceci:

MY_VAR=12345 
. ./testenv.sh
utilisateur1477
la source
2
Le lui ./dit exactement où se trouve le fichier, sans cela, bash examinera d'abord PATH, puis essaiera le répertoire en cours s'il ne l'a pas trouvé. Si bash est en cours d'exécution en mode POSIX et que vous ne fournissez pas de chemin d'accès au fichier (comme ./), la recherche s'effectue uniquement dans PATH et le fichier ne sera pas trouvé si le répertoire en cours n'est pas dans PATH.
geirha
@geirha Oui, vous avez raison, source(et .) fait va vérifier d' abord $ PATH, même si elles ne sont pas vraiment en cours d' exécution du script dans le sens habituel. Mon (ancien) commentaire était incorrect.
Eliah Kagan
Court et précis +1
David Morales