Universal Node.js shebang?

42

Node.js ist heutzutage sehr beliebt und ich habe einige Skripte darüber geschrieben. Leider ist die Kompatibilität ein Problem. Offiziell sollte der Node.js-Interpreter aufgerufen werden node, aber Debian und Ubuntu liefern nodejsstattdessen eine ausführbare Datei namens .

Ich möchte portable Skripte, mit denen Node.js in möglichst vielen Situationen arbeiten kann. Angenommen, der Dateiname lautet " foo.jsIch möchte wirklich, dass das Skript auf zwei Arten ausgeführt wird:

  1. ./foo.jsFührt das Skript aus, wenn entweder nodeoder vorhanden nodejsist $PATH.
  2. node foo.jsführt auch das Skript aus (vorausgesetzt, der Interpreter wird aufgerufen node)

Hinweis: Die Antworten von xavierm02 und mir sind zwei Variationen eines mehrsprachigen Skripts. Ich bin immer noch an einer reinen Shebang-Lösung interessiert, falls es eine gibt.

dancek
quelle
Ich denke, es gibt keine wirkliche Lösung dafür, da man die ausführbare Datei von den Build-Systemen beliebig benennen darf. Nichts hindert Sie daran, den Python-Interpreter alphacentauri zu benennen. Folgen Sie einfach den Konventionen und nennen Sie ihn Python. Ich würde vorschlagen, entweder einen Standardnamen nodefür Ihr Skript zu verwenden oder eine Art make-Skript zu verwenden, das den shebang modifiziert.
@ G.Kayaalp Abgesehen von Politik und Konventionen gibt es viele Debian / Ubuntu / Fedora-Benutzer, und ich möchte Skripte erstellen, die für sie funktionieren. Ich möchte hierfür kein Build-System einrichten (wer erstellt Shell-Skripte, bevor er sie ausführt?), Noch möchte ich alphacentauriund so weiter unterstützen. Wenn eine ausführbare Datei aufgerufen wird nodejs, können Sie zu 99% sicher sein, dass es sich um Node.js handelt. Warum nicht beide nodejsund unterstützen node?
Dancek
Installieren Sie das NodeJS-Legacy-Paket. Der Grund dafür ist, dass der Name zu arrogant ist, jemand anderes hat den Namen zuerst. Dieses andere Paket war jedoch bereit, den Namen zu teilen.
user3710044

Antworten:

54

Das Beste, was ich mir ausgedacht habe, ist dieses "Two-Line-Shebang", das wirklich ein vielsprachiges Skript (Bourne-Shell / Node.js) ist:

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

console.log('Hello world!');

Die erste Zeile ist offensichtlich ein Bourne-Shell-Shebang. Node.js umgeht jeden gefundenen Schebang. Für Node.js handelt es sich also um eine gültige Javascript-Datei.

Die zweite Zeile ruft die Shell no-op :mit dem Argument auf //und führt dann nodejsoder nodemit dem Namen dieser Datei als Parameter aus. command -vwird anstelle von whichfür die Portabilität verwendet. Die Befehlssubstitutionssyntax $(...)ist nicht ausschließlich Bourne, entscheiden Sie sich also für Backticks, wenn Sie diese in den 1980er Jahren ausführen.

Node.js wertet nur den String aus ':', der wie ein No-Op ist, und der Rest der Zeile wird als Kommentar analysiert.

Der Rest der Datei ist einfach altes Javascript. Die Subshell wird nach Abschluss der execzweiten Zeile beendet, sodass der Rest der Datei nie von der Shell gelesen wird.

Vielen Dank an xavierm02 für die Inspiration und alle Kommentatoren für zusätzliche Informationen!

dancek
quelle
1
Großartige Lösung. Eine Alternative zu diesem ':'Ansatz ist die Verwendung // 2>/dev/null(was auch immer der nvmFall ist): Beim Bash ist es ein Fehler ( bash: //: Is a directory), den die Umleitung im 2>/dev/nullHintergrund ignoriert. Für JavaScript wird die gesamte Zeile zu einem Kommentar. Außerdem - und ich erwarte nicht, dass dies ein Problem für IRL ist - commandgibt es eine Besonderheit, bei der auch Shell-Funktionen und Aliase mit gemeldet werden -v- auch wenn sie nicht tatsächlich aufgerufen werden. Wenn Sie also zufällig Shell-Funktionen oder sogar Aliase (vorausgesetzt shopt -s expand_aliases) mit dem Namen nodeoder exportiert haben, kann dies zu Problemen führen nodejs.
mklement0
1
Nun, POSIX sh ist heutzutage allgegenwärtig (mit Ausnahme von Solaris / bin / sh - Solaris wird jedoch mit AT & T ksh ausgeliefert, das POSIX-kompatibel ist), und Markus Kuhn empfiehlt (mit Recht), es nicht zu verwenden. IMHO brauchst du es nie . Aber ja, es ist genug für mksh, bash, dashund andere Shells in modernen Unix und GNU - Systemen.
Mirabilos
1
Tolle Lösung; obwohl noch tragbarer wäre zu verwenden, #!/usr/bin/env shanstatt #!/bin/sh(per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089
2
Ich würde mit Werbung #!/usr/bin/env shals "portabler" vorsichtig sein . Das gleiche Wikipedia - Artikel sagt : „Das funktioniert vor allem , weil der Pfad / usr / bin / env gemeinsam für das env - Dienstprogramm verwendet wird ...“ Das ist nicht eine wunderbare Bestätigung, und meine Vermutung ist , dass Sie in die Systeme laufen werden , ohne /usr/bin/envhäufiger als Sie stoßen auf Systeme ohne /bin/sh, wenn es überhaupt Unterschiede in der Frequenz gibt.
Sheldonh
1
@dancek Hallo von 2018, wäre //bin/sh -c :; exec ...eine sauberere Version der zweiten Zeile nicht?
har-wradim
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseist das Gleiche wie mit der /bin/falseAusnahme, dass der zweite Schrägstrich ihn in einen Kommentar für node umwandelt, und deshalb ist er hier. Dann wird die rechte Seite der ersten ||ausgewertet. 'which node || which nodejs'Mit Anführungszeichen anstelle von Anführungszeichen wird der Knoten gestartet und der <<Feed wird angezeigt, was auch immer auf der rechten Seite ist. Ich hätte ein Trennzeichen verwenden können, das //wie Dancek anfängt. Es hätte funktioniert, aber ich finde, dass es sauberer ist, wenn am Anfang nur zwei Zeilen stehen. Daher habe ich tail -n +2 $0die Datei bis auf die ersten beiden Zeilen selbst gelesen.

Wenn Sie es in node ausführen, wird die erste Zeile als "shebang" erkannt und ignoriert, und die zweite Zeile ist ein einzeiliger Kommentar.

(Anscheinend könnte sed verwendet werden, um den Inhalt der Tail Print-Datei ohne die erste und letzte Zeile zu ersetzen. )


Antwort vor dem Bearbeiten:

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

Sie können nicht tun, was Sie wollen. Stattdessen führen Sie ein Shell-Skript aus #!/bin/sh. Dieses Shell-Skript erhält den Pfad der Datei, die zum Ausführen des Knotens benötigt wird which node || which nodejs. Die Backquotes sind hier, damit es ausgeführt wird, also 'which node || which nodejs'ruft (mit den Backquotes anstelle der Quotes) einfach node auf. Dann füttern Sie einfach Ihr Skript damit <<. Das __HERE__sind die Begrenzer Ihres Skripts. Und das console.log('ok');ist ein Beispiel für ein Skript, das Sie durch Ihr Skript ersetzen sollten.

xavierm02
quelle
Eine Erklärung wäre nett
0xC0000022L
Besser geben Sie die hier-Dokument Trennzeichen zu vermeiden Parameter, Kommando - Substitutionen und arithmetische Expansion auf dem JavaScript - Code vor der Ausführung durchgeführt werden: <<'__HERE__'.
Manatwork
Das wird interessant! Obwohl //bin/falsees in meiner MSYS-Umgebung nicht funktioniert und ich Anführungszeichen für die Backticks benötige, da ich bei nodewohnt C:\Program Files\.... Ja, ich arbeite in einer schrecklichen Umgebung ...
Dancek
1
//bin/falsefunktioniert auch unter Mac OS X nicht. Es tut mir leid, aber das scheint nicht mehr tragbar zu sein.
Dancek
2
Der whichBefehl ist auch nicht portierbar.
Jordan
9

Dies ist nur ein Problem auf Debian-basierten Systemen, bei denen die Richtlinien den Sinn überstiegen.

Ich weiß nicht, wann Fedora eine Binärdatei namens nodejs bereitgestellt hat, aber ich habe sie nie gesehen. Das Paket heißt nodejs und installiert eine Binärdatei namens node.

Verwenden Sie einfach einen Symlink, um gesunden Menschenverstand auf Ihre Debian-basierten Systeme anzuwenden, und dann können Sie einen vernünftigen Schebang verwenden. Andere Leute werden sowieso vernünftige Scheiße benutzen, also wirst du diesen Symlink brauchen.

#!/usr/bin/env node

console.log("Spread the love.");
sheldonh
quelle
Es tut mir leid, aber das beantwortet die Frage nicht. Ich verstehe den politischen Standpunkt, aber das ist hier nicht der Punkt.
Dancek
Für die Aufzeichnung können manche Fedora - Systeme eine haben nodejsausführbar , aber das ist nicht Fedora Schuld. Entschuldigung für die falsche Darstellung der Tatsache.
Dancek
8
Keine Notwendigkeit, sich zu entschuldigen. Du liegst richtig. Meine Antwort beantwortet Ihre Frage nicht. Es spricht für Ihre Frage, was darauf hindeutet, dass die Frage den Anwendungsbereich auf weniger nützliche Lösungen beschränkt, als verfügbar sind. Ich biete praktische Ratschläge, die von der Geschichte geprägt sind. Node ist nicht der erste Dolmetscher, der sich hier findet. Sie sollten den Tumult gesehen haben, der dazu geführt hat, dass Larry Wall erklärt hat, dass der Perl-Shebang sein MUSS #!/usr/bin/perl. Das heißt, Sie sind nicht verpflichtet, meine Vorschläge zu mögen oder anzuwenden. Frieden.
Sheldonh
3

Wenn es Ihnen nichts ausmacht, eine kleine .shDatei zu erstellen, habe ich eine kleine Lösung für Sie. Sie können ein kleines Shell-Skript erstellen, um zu bestimmen, welche Knoten-Programmdatei verwendet werden soll, und dieses Skript in Ihrem shebang verwenden:

shebang.sh :

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

script.js :

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

Markieren Sie beide ausführbaren Dateien und führen Sie sie aus ./script.js.

Auf diese Weise vermeiden Sie mehrsprachiges Scripting. Ich denke nicht, dass die Verwendung mehrerer Shebang-Linien möglich ist, obwohl dies eine gute Idee zu sein scheint.

Obwohl dies das Problem auf die von Ihnen gewünschte Weise löst, scheint es, dass sich niemand darum kümmert. Zum Beispiel uglifyjs und Coffeescript Anwendungen #!/usr/bin/env node, npm verwendet einen Shell - Skript als Einstiegspunkt, der wiederum die ausführbare Datei mit dem Namen explizit aufruft node. Ich bin ein Ubuntu-Benutzer und wusste das nicht, da ich den Knoten immer kompiliere. Ich denke darüber nach, dies als Fehler zu melden.


quelle
Ich bin mir ziemlich sicher, dass Sie den Benutzer zumindest chmod +x
auffordern
@ xavierm02 Man kann das Script chmod +x'd freigeben . Und ich stimme zu, dass eine NODE-Variable besser ist als which node || which nodejs. Der Anfragende möchte jedoch eine sofort einsatzbereite Erfahrung bieten, obwohl viele große Knotenprojekte nur diese verwenden #!/usr/bin/env node.
1

Der Vollständigkeit halber gibt es hier ein paar andere Möglichkeiten, die zweite Zeile zu machen:

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

Beides hat aber keinen Vorteil gegenüber der gewählten Antwort:

':' //; || echo yes

Wenn Sie wüssten, dass entweder nodeoder nodejs(aber nicht beide) gefunden werden, funktioniert Folgendes:

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

Aber das ist ein großes "Wenn", daher denke ich, dass die ausgewählte Antwort die beste bleibt.

Dave Mason
quelle
Darüber hinaus können Sie jeden Befehl ausführen, foobar // 2>/dev/nullsolange er foobarkein Befehl ist. Und viele der auf jedem POSIX-System gefundenen Dienstprogramme können mit diesem //Argument ausgeführt werden.
Tanz
1

Ich verstehe, dass dies die Frage nicht beantwortet, aber ich bin überzeugt, dass die Frage mit einer falschen Prämisse gestellt wird.

Ubuntu ist hier falsch. Das Schreiben eines universellen Shebangs für Ihr eigenes Skript ändert nichts an anderen Paketen, über die Sie keine Kontrolle haben und die den De-facto-Standard von verwenden #!/usr/bin/env node. Ihr System muss liefern nodein , PATHwenn es für alle Skripte wünscht NodeJS Targeting darauf laufen.

Beispielsweise npmschreibt nicht einmal das von Ubuntu bereitgestellte Paket Shebangs in Pakete um:

$ 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
quelle