Comment fonctionne la recherche dans $ PATH sous le capot?

8

Il y a beaucoup trop d'articles / ressources sur le Web qui apprennent aux gens comment définir la variable d'environnement PATHafin qu'ils puissent utiliser la main courte de javaou pythonetc au lieu du chemin absolu dans l'interface de ligne de commande.

Ce que je suis intéressé de savoir, c'est ce qui se cache derrière la scène lorsque nous tapons la commande et appuyons sur Entrée (similaire à ce qui se passe lorsque vous tapez une URL dans le navigateur ).

Voici ma conjecture:

  1. lire la commande (analyser / prétraiter stdin pour obtenir les bons arguments $@)
  2. recherche de commande
  3. exécution de la commande (programme démarré, consommer de la mémoire, stdout / stderr pour shell)
  4. re-rendre l'émulateur par les variables d'environnement pertinentes (par exemple $PS#, $PROMPTetc.)

La partie que je veux comprendre le plus est la recherche de commande. De toute évidence, le $PATHest consommé par une fonction d'arrière-plan et séparé par :/ ;comme délimiteurs, alors que s'est-il passé? Utilisons-nous une table de hachage (clé: nom de base du fichier, valeur: nom de répertoire absolu du fichier) pour stocker les fichiers binaires sous ces chemins, ou d'autres crochets?

REMARQUE: je pensais à l'origine qu'il s'agissait d'une table de hachage car je peux utiliser [ -z hash [command] ]pour vérifier si une commande est disponible dans env actuel, mais lorsque j'utilise, hash | grep pythonje n'obtiens rien de la sortie tout en which pythontravaillant comme prévu. (Je pense que le mécanisme peut être spécifique au shell, mais je veux en savoir plus.)

Xlee
la source

Réponses:

11

Comme vous le pensez, le comportement exact dépend du shell, mais un niveau de fonctionnalité de base est spécifié par POSIX.

La recherche et l'exécution de commandes pour le langage de commande shell standard (dont la plupart des shells implémentent un sur-ensemble) ont beaucoup de cas, mais nous ne sommes intéressés pour le moment que par le cas où PATHest utilisé. Dans ce cas:

la commande doit être recherchée à l'aide de la variable d'environnement PATH comme décrit dans Variables d'environnement XBD

et

Si la recherche réussit:

[...]

le shell exécute l'utilitaire dans un environnement utilitaire séparé avec des actions équivalentes à l'appel de la execl()fonction [...] avec l' argument path défini sur le chemin d'accès résultant de la recherche.

En cas d'échec, l'exécution échoue et un code de sortie de 127 est renvoyé avec un message d'erreur.

Ce comportement est conforme à la execvpfonction, en particulier. Toutes les exec*fonctions acceptent le nom de fichier d'un programme à exécuter, une séquence d'arguments (qui sera celle argvdu programme), et peut-être un ensemble de variables d'environnement. Pour les versions utilisant la PATHrecherche, POSIX définit que :

Le fichier d' arguments est utilisé pour construire un nom de chemin qui identifie le nouveau fichier image de processus [...] le préfixe de chemin pour ce fichier est obtenu par une recherche dans les répertoires passés comme variable d'environnement PATH


Le comportement de PATH est défini ailleurs comme:

Cette variable doit représenter la séquence de préfixes de chemin que certaines fonctions et utilitaires appliquent lors de la recherche d'un fichier exécutable connu uniquement par un nom de fichier. Les préfixes doivent être séparés par un <colon> (':'). Lorsqu'un préfixe de longueur non nulle est appliqué à ce nom de fichier, une <barre oblique> doit être insérée entre le préfixe et le nom de fichier si le préfixe ne se termine pas par. Un préfixe de longueur nulle est une fonction héritée qui indique le répertoire de travail actuel. Il apparaît sous la forme de deux caractères adjacents ("::"), en tant que <colon> initial précédant le reste de la liste, ou en tant que <colon> de fin après le reste de la liste. Une application strictement conforme doit utiliser un nom de chemin réel (tel que.) Pour représenter le répertoire de travail actuel dans PATH.La liste doit être recherchée du début à la fin, en appliquant le nom de fichier à chaque préfixe, jusqu'à ce qu'un fichier exécutable avec le nom spécifié et les autorisations d'exécution appropriées soit trouvé . Si le chemin recherché contient une <barre oblique>, la recherche dans les préfixes de chemin ne doit pas être effectuée. Si le nom de chemin commence par une <slash>, le chemin spécifié est résolu (voir Résolution de nom de chemin ). Si PATH n'est pas défini ou est défini sur null, la recherche de chemin est définie par l'implémentation.

C'est un peu dense, donc un résumé:

  1. Si le nom du programme contient une /(barre oblique, U + 002F SOLIDUS), traitez-le comme un chemin d'accès de la manière habituelle et ignorez le reste de ce processus. Pour le shell, ce cas ne se pose techniquement pas (car les règles du shell l'auront déjà traité).
  2. La valeur de PATHest divisée en morceaux à chaque deux-points, puis chaque composant est traité de gauche à droite. En tant que cas spécial (historique), un composant vide d'une variable non vide est traité comme .(le répertoire courant).
  3. Pour chaque composant, le nom du programme est ajouté à la fin avec une jointure /et l'existence d'un fichier de ce nom est vérifiée, et s'il en existe un, les autorisations d'exécution valides (+ x) sont également vérifiées. Si l'une de ces vérifications échoue, le processus passe au composant suivant. Sinon, la commande résout ce chemin et la recherche est effectuée.
  4. Si vous manquez de composants, la recherche échoue.
  5. S'il n'y a rien PATHou s'il n'existe pas, faites ce que vous voulez.

Les vrais shells auront des commandes intégrées, qui se trouvent avant cette recherche, et souvent aussi des alias et des fonctions. Ceux-là n'interagissent pas avec PATH. POSIX définit certains comportements autour de ceux-ci , et votre shell peut en avoir beaucoup plus.


Bien qu'il soit possible de compter exec*pour faire la plupart de cela pour vous, le shell peut en pratique implémenter cette recherche lui-même, notamment à des fins de mise en cache, mais le comportement de cache vide devrait être similaire. Les coquilles ont une latitude assez large ici et ont des comportements subtilement différents dans les cas d'angle.

Comme vous l'avez trouvé, Bash utilise une table de hachage pour se souvenir des chemins complets des commandes qu'il a vu précédemment, et cette table est accessible avec la hashfonction. La première fois que vous exécutez une commande, elle recherche et lorsqu'un résultat est trouvé, il est ajouté à la table, il n'est donc pas nécessaire de chercher la prochaine fois que vous l'essayez.

Dans zsh, en revanche, le full PATHest généralement recherché au démarrage du shell. Une table de recherche est préremplie avec tous les noms de commande découverts afin que les recherches d'exécution ne soient généralement pas nécessaires (sauf si une nouvelle commande est ajoutée). Vous pouvez remarquer que cela se produit lorsque vous essayez de compléter par tabulation une commande qui n'existait pas auparavant.

Les shells très légers, par exemple dash, ont tendance à déléguer autant de comportements que possible à la bibliothèque système et ne se soucient pas de se souvenir des chemins de commandes passés.

Michael Homer
la source
Un grand merci pour une explication aussi détaillée, cela donne vraiment un aperçu profond. Votre comparaison PATHentre bashet zshm'aide à résoudre ma confusion!
Xlee