Universal Node.js shebang?

42

Node.js est très populaire ces jours-ci et j'ai écrit quelques scripts dessus. Malheureusement, la compatibilité est un problème. Officiellement, l'interpréteur Node.js est censé être appelé node, mais Debian et Ubuntu fournissent à la nodejsplace un exécutable appelé .

Je veux des scripts portables que Node.js puisse utiliser dans autant de situations que possible. En supposant que le nom de fichier soit foo.js, je veux vraiment que le script s'exécute de deux manières:

  1. ./foo.jsexécute le script si nodeou nodejsest dans $PATH.
  2. node foo.jsexécute également le script (en supposant que l'interprète soit appelé node)

Remarque: les réponses de xavierm02 et de moi-même sont deux variantes d'un script polyglotte. Je suis toujours intéressé par une solution pure de shebang, si une telle existe.

dancek
la source
Je pense qu'il n'y a pas de vraie solution à cela, étant donné qu'il est permis de nommer l'exécutable de manière arbitraire par les systèmes de compilation. Rien ne vous empêche de nommer alphacentauri, un interpréteur python, il vous suffit de suivre les conventions et de le nommer python. Je suggérerais d'utiliser un nom standard nodepour votre script ou d'avoir une sorte de script de création qui modifie le shebang.
@ G.Kayaalp La politique et les conventions mises à part, il y a beaucoup d'utilisateurs Debian / Ubuntu / Fedora et je veux créer des scripts qui leur conviennent. Je ne veux pas installer un système de compilation pour cela (celui qui construit les scripts shell avant de les exécuter?), Je ne veux pas non plus prendre en charge alphacentauri, etc. Si un exécutable est appelé nodejs, vous pouvez être sûr à 99% que c'est Node.js. Pourquoi ne pas soutenir les deux nodejset node?
dancek
Installez le paquet nodejs-legacy. La raison en est que le nom est trop générique, quelqu'un d'autre a eu le nom en premier. Cet autre paquet était cependant disposé à partager le nom.
user3710044

Réponses:

54

Le meilleur que j'ai créé est ce script "à deux lignes" qui est vraiment un script polyglotte (Bourne shell / Node.js):

#!/bin/sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

console.log('Hello world!');

La première ligne est, évidemment, un shebang shell Bourne. Node.js contourne tous les shebang qu'il trouve, il s'agit donc d'un fichier javascript valide en ce qui concerne Node.js.

La deuxième ligne appelle le shell no-op :avec l'argument //, puis s'exécute nodejsou nodeavec le nom de ce fichier en tant que paramètre. command -vest utilisé à la place de whichpour la portabilité. La syntaxe de substitution de commande $(...)n'est pas strictement Bourne, optez donc pour des backticks si vous l'exécutez dans les années 1980.

Node.js évalue simplement la chaîne ':', ce qui est comme un non-op, et le reste de la ligne est analysé comme un commentaire.

Le reste du fichier est simplement du vieux javascript. Le sous-shell se ferme à la fin de la execdeuxième ligne, le reste du fichier n'est donc jamais lu par le shell.

Merci à xavierm02 pour son inspiration et à tous les commentateurs pour des informations supplémentaires!

dancek
la source
1
Excellente solution. Une alternative à l' ':'approche consiste à utiliser // 2>/dev/null(ce qui est le nvmcas): bash, c'est une erreur ( bash: //: Is a directory), que la redirection 2>/dev/nullignore discrètement; en JavaScript, toute la ligne devient un commentaire. En outre - et je ne m'attends pas à ce que ce soit un problème, IRL - commandprésente une bizarrerie: il signale également les fonctions de shell et les alias -v, même s'il ne les invoque pas réellement. Ainsi, si vous avez exporté des fonctions de shell ou même des alias (supposés shopt -s expand_aliases) nommés nodeou nommés nodejs, les choses risquent de casser.
mklement0
1
Eh bien, POSIX sh est omniprésent de nos jours (sauf Solaris / bin / sh - mais Solaris est livré avec AT & T ksh prêt à l'emploi, qui est compatible POSIX), et Markus Kuhn recommande (avec une justification) de ne pas l' utiliser . IMHO vous n'en avez jamais besoin. Mais oui, il est assez pour mksh, bash, dashet d' autres coquilles dans les systèmes Unix et GNU modernes.
mirabilos
1
Excellente solution; bien que plus portable encore serait d'utiliser à la #!/usr/bin/env shplace de #!/bin/sh(par en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089
2
Je ferais attention à la publicité #!/usr/bin/env shcomme "plus portable". Ce même article de Wikipedia dit "Cela fonctionne principalement parce que le chemin / usr / bin / env est couramment utilisé pour l'utilitaire env ..." Ce n'est pas une approbation merveilleuse, et je suppose que vous rencontrerez des systèmes sans /usr/bin/envplus vous rencontrez des systèmes sans /bin/shaucune différence de fréquence.
Sheldonh
1
@dancek Hi à partir de 2018, ne serait-il pas //bin/sh -c :; exec ...une version plus propre de la deuxième ligne?
Har-Wradim
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseest la même chose que /bin/falsesauf que la deuxième barre oblique la transforme en un commentaire pour le noeud, et c'est pourquoi elle est ici. Ensuite, le côté droit du premier ||est évalué. 'which node || which nodejs'avec des guillemets au lieu de guillemets, lance le nœud et le <<filme tout ce qui se trouve à droite. J'aurais pu utiliser un délimiteur commençant par " //dancek", cela aurait fonctionné, mais je trouve plus propre de ne comporter que deux lignes au début, alors je tail -n +2 $0lisais le fichier lui-même, à l'exception des deux premières lignes.

Et si vous l'exécutez dans node, la première ligne est reconnue comme un shebang et ignorée et la seconde est un commentaire d'une ligne.

(Apparemment, sed pourrait être utilisé pour remplacer le contenu du fichier d'impression arrière sans les première et dernière lignes )


Répondre avant édition:

#!/bin/sh
`which node || which nodejs` <<__HERE__
console.log('ok');
__HERE__

Vous ne pouvez pas faire ce que vous voulez, vous devez donc exécuter un script shell, d’où le fichier #!/bin/sh. Ce script shell obtiendra le chemin du fichier nécessaire à l'exécution du noeud, c'est-à-dire which node || which nodejs. Les citations arrières sont ici pour qu'il soit exécuté, ainsi 'which node || which nodejs'(avec les citations arrières au lieu des guillemets) appelle simplement noeud. Ensuite, vous alimentez simplement votre script avec <<. Ce __HERE__sont les délimiteurs de votre script. Et console.log('ok');voici un exemple de script que vous devriez remplacer par votre script.

xavierm02
la source
une explication serait bien
0xC0000022L
Mieux citer le code JavaScript sur le delimiter ici-document pour éviter l' expansion des paramètres, la substitution de commande et l' évaluation arithmétique à effectuer avant l' exécution: <<'__HERE__'.
Manatwork
Cela devient intéressant! Bien que //bin/falsecela ne fonctionne pas dans mon environnement MSYS, et j'ai besoin de guillemets sur les backticks en tant que mon lieu de noderésidence C:\Program Files\.... Oui, je travaille dans un environnement horrible ...
dancek
1
//bin/falsene fonctionne pas sur Mac OS X non plus. Je suis désolé, mais cela ne semble plus très portable.
dancek
2
La whichcommande n'est pas non plus portable.
Jordanie
9

Ce problème ne concerne que les systèmes basés sur Debian, où la politique a dépassé le sens.

Je ne sais pas quand Fedora a fourni un binaire appelé nodejs, mais je ne l'ai jamais vu. Le paquet s'appelle nodejs et installe un binaire appelé noeud.

Utilisez simplement un lien symbolique pour appliquer le bon sens à vos systèmes basés sur Debian, puis vous pourrez utiliser un shebang sain d'esprit. De toute façon, d'autres personnes utiliseront des shebangs sensés, vous aurez donc besoin de ce lien symbolique.

#!/usr/bin/env node

console.log("Spread the love.");
Sheldonh
la source
Je suis désolé, mais cela ne répond pas à la question. Je comprends le point de vue politique, mais ce n’est pas le propos ici.
dancek
Pour mémoire, certains systèmes Fedora ont un nodejsexécutable , mais ce n’est pas la faute de Fedora. Désolé pour déformer le fait.
dancek
8
Pas besoin de s'excuser. Tu as plutot raison. Ma réponse ne répond pas à votre question. Cela répond à votre question, suggérant que la question limite la portée à des solutions moins utiles que celles disponibles. Je propose des conseils pratiques éclairés par l'histoire. Node n'est pas le premier interprète à se retrouver ici. Vous auriez dû voir le tumulte qui a conduit Larry Wall à déclarer que le perl shebang DOIT être #!/usr/bin/perl. Cela dit, vous n'êtes pas obligé d'aimer ou d'appliquer mes suggestions. Paix.
Sheldonh
3

Si vous voulez bien créer un petit .shfichier, j'ai une petite solution pour vous. Vous pouvez créer un petit script shell pour déterminer le noeud exécutable à utiliser, et utiliser ce script dans votre shebang:

shebang.sh :

#!/bin/sh
`which node || which nodejs` $@

script.js :

#!./shebang.sh
console.log('hello');

Marquez les deux exécutables et lancez ./script.js.

De cette façon, vous évitez les scripts polyglottes. Je ne pense pas qu'il soit possible d'utiliser plusieurs lignes de shebang, bien que cela semble être une bonne idée.

Même si cela résout le problème comme vous le souhaitez, il semble que personne ne s'en soucie. Par exemple, uglifyjs et coffeescript utilisent #!/usr/bin/env node, npm utilise un script shell comme point d’entrée, qui appelle à nouveau explicitement l’exécutable avec le nom node. Je suis un utilisateur Ubuntu et je ne le savais pas car je compile toujours des nœuds. J'envisage de signaler ceci comme un bug.


la source
Je suis sûr que vous devrez au moins demander à l'utilisateur de chmod +xle script sh ... vous pouvez donc aussi leur demander de définir une variable donnant l'emplacement de leur nœud exécutable ...
xavierm02
@ xavierm02 On peut libérer le script chmod +x'd. Et je conviens qu’une variable NODE est meilleure que which node || which nodejs. Mais l'enquêteur souhaite offrir une expérience immédiate, bien que de nombreux grands projets de nœuds l'utilisent #!/usr/bin/env node.
1

Par souci d’exhaustivité, voici deux autres façons de faire la deuxième ligne:

// 2>/dev/null || echo yes
false //|| echo yes

Mais ni l'un ni l'autre n'a aucun avantage sur la réponse choisie:

':' //; || echo yes

De plus, si vous saviez que l'un nodeou l' autre nodejs(mais pas les deux) serait trouvé, les travaux suivants:

exec `command -v node nodejs` "$0" "$@"

Mais c'est un gros "si", alors je pense que la réponse choisie reste la meilleure.

Dave Mason
la source
De plus, vous pouvez exécuter n'importe quelle commande foobar // 2>/dev/nulltant qu'elle foobarn'est pas une commande. Et bon nombre des utilitaires présents sur n’importe quel système POSIX peuvent être exécutés avec l’ //argument.
dancek
1

Je comprends que cela ne répond pas à la question, mais je suis convaincu que la question est posée avec une fausse prémisse.

Ubuntu se trompe ici. Écrire un shebang universel pour votre propre script ne changera pas les autres packages sur lesquels vous n'avez aucun contrôle sur ceux qui utilisent le standard de facto #!/usr/bin/env node. Votre système doit fournir nodeen PATHsi elle le souhaite pour tous les scripts ciblant nodejs pour fonctionner sur elle.

Par exemple, même le npmpaquet fourni par Ubuntu ne réécrit pas les shebangs dans des paquets:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- [email protected]
  `-- [email protected]

npm WARN test_npm No description
npm WARN test_npm No repository field.
npm WARN test_npm No README data
npm WARN test_npm No license field.
$ node_modules/.bin/mkdirp testdir
/usr/bin/env: 'node': No such file or directory
$ head -n1 node_modules/.bin/mkdirp
#!/usr/bin/env node
$ npm --version
3.5.2
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
Binki
la source