Que fait exactement «/ usr / bin / env node» au début des fichiers de nœuds?

109

J'avais vu cette ligne #!/usr/bin/env nodeau début de quelques exemples dans nodejset j'avais cherché sur Google sans trouver de sujet qui pourrait répondre à la raison de cette ligne.

La nature des mots ne facilite pas la recherche.

J'en avais lu quelques javascript- uns et des nodejslivres récemment et je ne me souvenais pas l'avoir vu dans aucun d'entre eux.

Si vous voulez un exemple, vous pouvez voir le tutorielRabbitMQ officiel , ils l'ont dans presque tous leurs exemples, en voici un:

#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

amqp.connect('amqp://localhost', function(err, conn) {
  conn.createChannel(function(err, ch) {
    var ex = 'logs';
    var msg = process.argv.slice(2).join(' ') || 'Hello World!';

    ch.assertExchange(ex, 'fanout', {durable: false});
    ch.publish(ex, '', new Buffer(msg));
    console.log(" [x] Sent %s", msg);
  });

  setTimeout(function() { conn.close(); process.exit(0) }, 500);
});

Quelqu'un pourrait-il m'expliquer quelle est la signification de cette ligne?

Quelle est la différence si je mets ou supprime cette ligne? Dans quels cas en ai-je besoin?

Gepser
la source
3
il prend essentiellement l'environnement du shell appelant et le fourre dans n'importe quelle application spécifiée. dans ce cas,node
Marc B
En fait non, je ne viens pas de Windows, mais merci d'avoir mis à jour votre réponse. J'attends juste de voir si quelqu'un d'autre vient avec une opinion différente. Il y a juste une chose que je pense que vous n'avez pas mentionnée dans votre réponse, je l'ai trouvée il y a quelques heures. Les choses qu'ils mentionnent ici semblent assez importantes, mais ce n'est pas encore assez clair pour moi. stackoverflow.com/questions/14517535/… (vous pouvez mettre à jour si vous le souhaitez, je l'apprécierai vraiment, mais ne le sentez pas comme une obligation, votre réponse est assez bonne pour le moment).
Gepser
@Gepser: Compris. En bref, si vous souhaitez npminstaller un script source Node.js en tant que CLI (potentiellement disponible dans le monde entier) , vous devez utiliser une ligne shebang - et npmcela fonctionnera même sous Windows; voir ma réponse mise à jour une fois de plus.
mklement0
"La nature des mots rend la recherche pas si facile" - vous pouvez essayer duckduckgo.com pour ce cas d'utilisation de recherche spécifique
Ricardo

Réponses:

146

#!/usr/bin/env nodeest une instance d'une ligne shebang : la toute première ligne d'un fichier exécutable en texte brut sur des plates-formes de type Unix qui indique au système à quel interpréteur passer ce fichier pour exécution , via la ligne de commande suivant le #!préfixe magique (appelé shebang ) .

Remarque: Windows ne prend pas en charge les lignes shebang , elles y sont donc effectivement ignorées ; sous Windows, c'est uniquement l' extension de nom de fichier d' un fichier donné qui détermine quel exécutable l'interprétera. Cependant, vous en avez toujours besoin dans le contexte denpm . [1]

La discussion générale suivante sur les lignes shebang est limitée aux plates-formes de type Unix:

Dans la discussion suivante, je suppose que le fichier contenant le code source à exécuter par Node.js est simplement nommé file.

  • Vous avez BESOIN de cette ligne , si vous voulez appeler un fichier source Node.js directement , en tant qu'exécutable à part entière - cela suppose que le fichier a été marqué comme exécutable avec une commande telle que chmod +x ./file, qui vous permet ensuite d'appeler le fichier avec, par exemple, ./fileou, s'il se trouve dans l'un des répertoires répertoriés dans la $PATHvariable, simplement sous la forme file.

    • Plus précisément, vous avez besoin d'une ligne shebang pour créer des CLI basées sur les fichiers source Node.js dans le cadre d'un package npm , avec la ou les CLI à installer en npmfonction de la valeur de la "bin"clé dans le package.jsonfichier d' un package ; Consultez également cette réponse pour savoir comment cela fonctionne avec les packages installés globalement . La note de bas de page [1] montre comment cela est géré sous Windows.
  • Vous n'avez PAS besoin de cette ligne pour appeler un fichier explicitement via l' nodeinterpréteur, par exemple,node ./file


Informations générales facultatives :

#!/usr/bin/env <executableName>est un moyen de spécifier un interpréteur de manière portable : en un mot, il dit: exécutez <executableName>partout où vous le trouvez (en premier) parmi les répertoires listés dans la $PATHvariable (et passez-lui implicitement le chemin du fichier en question).

Cela explique le fait qu'un interpréteur donné peut être installé à différents endroits sur les plates-formes, ce qui est certainement le cas avec nodele binaire Node.js.

En revanche, l'emplacement de l' envutilitaire lui-même peut être considéré comme étant au même emplacement sur toutes les plates-formes, à savoir /usr/bin/env- et la spécification du chemin complet vers un exécutable est requise dans une ligne shebang.

Notez que l'utilitaire POSIX envest en cours de réutilisation ici pour localiser par nom de fichier et exécuter un exécutable dans le $PATH.
Le véritable objectif de envest de gérer l'environnement d'une commande - voir envles spécifications POSIX de et la réponse utile de Keith Thompson .


Il est également intéressant de noter que Node.js crée une exception de syntaxe pour les lignes shebang, étant donné qu'il ne s'agit pas de code JavaScript valide (ce #n'est pas un caractère de commentaire en JavaScript, contrairement aux shells de type POSIX et autres interpréteurs).


[1] Dans l'intérêt de la cohérence multiplateforme, npmcrée des fichiers wrapper *.cmd (fichiers batch) sous Windows lors de l'installation des exécutables spécifiés dans le package.jsonfichier d' un package (via la "bin"propriété). Essentiellement, ces fichiers batch wrapper imitent la fonctionnalité Unix shebang: ils invoquent le fichier cible explicitement avec l'exécutable spécifié dans la ligne shebang - ainsi, vos scripts doivent inclure une ligne shebang même si vous avez l'intention de les exécuter uniquement sous Windows - voir cette réponse du mien pour plus de détails.
Étant donné que les *.cmdfichiers peuvent être appelés sans le.cmd, cela permet une expérience multiplateforme transparente: sous Windows et Unix, vous pouvez effectivement appeler une npmCLI installée par son nom d'origine, sans extension.

mklement0
la source
Pouvez-vous fournir une explication ou un résumé pour les nuls comme moi?
Andrew Lam
4
@AndrewLam: Sous Windows, les extensions de nom de fichier telles que .cmdet .pydéterminent quel programme sera utilisé pour exécuter ces fichiers. Sous Unix, la ligne shebang remplit cette fonction. Pour npmfonctionner sur toutes les plates-formes prises en charge, vous avez besoin de la ligne shebang, même sous Windows.
mklement0
28

Les scripts qui doivent être exécutés par un interpréteur ont normalement une ligne shebang en haut pour indiquer au système d'exploitation comment les exécuter.

Si vous avez un script nommé foodont la première ligne est #!/bin/sh, le système lira cette première ligne et exécutera l'équivalent de /bin/sh foo. Pour cette raison, la plupart des interpréteurs sont configurés pour accepter le nom d'un fichier de script comme argument de ligne de commande.

Le nom de l'interpréteur qui suit #!doit être un chemin complet; le système d'exploitation ne recherchera pas votre $PATHpour trouver l'interprète.

Si vous avez un script à exécuter node, la manière la plus évidente d'écrire la première ligne est:

#!/usr/bin/node

mais cela ne fonctionne pas si la nodecommande n'est pas installée dans /usr/bin.

Une solution de contournement courante consiste à utiliser la envcommande (qui n'était pas vraiment destinée à cet effet):

#!/usr/bin/env node

Si votre script est appelé foo, le système d'exploitation fera l'équivalent de

/usr/bin/env node foo

La envcommande exécute une autre commande dont le nom est donné sur sa ligne de commande, en passant les arguments suivants à cette commande. La raison pour laquelle il est utilisé ici est qu'il envrecherchera $PATHla commande. Donc, si nodeest installé dans /usr/local/bin/node, et que vous avez /usr/local/bindans votre $PATH, la envcommande invoquera /usr/local/bin/node foo.

L'objectif principal de la envcommande est d'exécuter une autre commande avec un environnement modifié, en ajoutant ou en supprimant des variables d'environnement spécifiées avant d'exécuter la commande. Mais sans arguments supplémentaires, il exécute simplement la commande avec un environnement inchangé, ce dont vous avez besoin dans ce cas.

Cette approche présente certains inconvénients. La plupart des systèmes modernes de type Unix l'ont fait /usr/bin/env, mais j'ai travaillé sur des systèmes plus anciens où la envcommande était installée dans un répertoire différent. Il peut y avoir des limitations sur les arguments supplémentaires que vous pouvez transmettre à l'aide de ce mécanisme. Si l'utilisateur n'a pas le répertoire contenant la nodecommande dans $PATH, ou si une commande différente est appelée node, alors il pourrait appeler la mauvaise commande ou ne pas fonctionner du tout.

D'autres approches sont:

  • Utilisez une #!ligne qui spécifie le chemin complet de la nodecommande elle-même, en mettant à jour le script selon les besoins pour différents systèmes; ou
  • Appelez la nodecommande avec votre script comme argument.

Voir aussi cette question (et ma réponse ) pour plus de discussion sur l' #!/usr/bin/envastuce.

Incidemment, sur mon système (Linux Mint 17.2), il est installé en tant que /usr/bin/nodejs. Selon mes notes, il est passé de /usr/bin/nodeà /usr/bin/nodejsentre Ubuntu 12.04 et 12.10. L' #!/usr/bin/envastuce n'aidera pas avec cela (à moins que vous ne configuriez un lien symbolique ou quelque chose de similaire).

MISE À JOUR: Un commentaire de mtraceur dit (reformaté):

Une solution de contournement pour le problème nodejs vs node consiste à démarrer le fichier avec les six lignes suivantes:

#!/bin/sh -
':' /*-
test1=$(nodejs --version 2>&1) && exec nodejs "$0" "$@"
test2=$(node --version 2>&1) && exec node "$0" "$@"
exec printf '%s\n' "$test1" "$test2" 1>&2
*/

Cela essaiera d'abord nodejs, puis essaiera node, et n'imprimera les messages d'erreur que si les deux ne sont pas trouvés. Une explication est hors de portée de ces commentaires, je la laisse juste ici au cas où cela aiderait quiconque à résoudre le problème puisque cette réponse a soulevé le problème.

Je n'ai pas utilisé NodeJS récemment. J'espère que le problème du nodejsvs nodea été résolu dans les années qui ont suivi la première publication de cette réponse. Sur Ubuntu 18.04, le nodejspackage s'installe en /usr/bin/nodejstant que lien symbolique vers /usr/bin/node. Sur certains systèmes d'exploitation antérieurs (Ubuntu ou Linux Mint, je ne sais pas lequel), il y avait un nodejs-legacypackage qui était fourni en nodetant que lien symbolique vers nodejs. Aucune garantie que j'ai tous les détails.

Keith Thompson
la source
Une réponse très complète expliquant pourquoi des choses.
Suraj Jain
1
Une solution de contournement pour le problème nodejsvs nodeconsiste à démarrer le fichier avec les six lignes suivantes: 1) #!/bin/sh -, 2) ':' /*-3) test1=$(nodejs --version 2>&1) && exec nodejs "$0" "$@"4) test2=$(node --version 2>&1) && exec node "$0" "$@", 5) exec printf '%s\n' "$test1" "$test2" 1>&26) */. Cela essaiera d'abord nodejs, puis essaiera node, et n'imprimera les messages d'erreur que si les deux ne sont pas trouvés. Une explication est hors de portée de ces commentaires, je la laisse juste ici au cas où cela aiderait quiconque à résoudre le problème puisque cette réponse a soulevé le problème.
mtraceur
@mtraceur: J'ai intégré votre commentaire dans ma réponse. Pourquoi le -sur la #!ligne?
Keith Thompson
Le -in #!/bin/sh -est juste une habitude qui garantit que le shell se comporte correctement dans l'ensemble extrêmement étroit et improbable de circonstances où le nom du script ou le chemin relatif que le shell voit commence par un -. (De plus, oui, il semble que toutes les distributions grand public étaient redevenues nodele nom principal. Je n'ai pas cherché à vérifier en faisant mon commentaire, mais pour autant que je sache, seul l'arbre généalogique de la distribution Debian utilisé nodejs, et il semble comme ils sont tous revenus au support nodeune fois que Debian l'a fait.)
mtraceur
Techniquement, le tiret unique en tant que premier argument ne signifiait pas "fin des options" - il signifiait à l'origine "désactiver -xet -v", mais depuis les premiers Bourne-likes n'ont analysé que le premier argument comme des options possibles, et puisque le shell commence avec ces options désactivées , il était abusif de faire en sorte que le shell n'essaie pas d'analyser le nom du script depuis l'original, et reste ainsi abusable car le comportement est maintenu dans les Bourne-likes modernes pour des raisons de compatibilité. Si je me souviens de toute mon histoire de Bourne et de mes anecdotes sur la portabilité.
mtraceur le
0

Réponse courte: c'est le chemin vers l'interprète.

EDIT (Réponse longue): La raison pour laquelle il n'y a pas de barre oblique avant "node" est que vous ne pouvez pas toujours garantir la fiabilité de #! / Bin /. Le bit "/ env" rend le programme plus multiplateforme en exécutant le script dans un environnement modifié et en étant plus fiable en mesure de trouver le programme d'interprétation.

Vous n'en avez pas forcément besoin, mais il est bon de l'utiliser pour assurer la portabilité (et le professionnalisme)

Quantum
la source
1
Le /usr/bin/envbit ne modifie pas l'environnement. C'est juste une commande à un emplacement (principalement) connu qui invoque une autre commande donnée en argument et cherche $PATHà la trouver. Le fait est que la #!ligne nécessite le chemin complet de la commande appelée, et vous ne savez pas nécessairement où nodeest installé.
Keith Thompson
C'est ce à quoi je voulais en venir, merci d'avoir clarifié!
Quantum