Universal Node.js shebang?


42

Node.js è molto popolare in questi giorni e ho scritto alcuni script su di esso. Sfortunatamente, la compatibilità è un problema. Ufficialmente, l'interprete Node.js dovrebbe essere chiamato node, ma Debian e Ubuntu forniscono nodejsinvece un eseguibile chiamato .

Voglio script portatili con cui Node.js possa lavorare il maggior numero possibile di situazioni. Supponendo che il nome del file sia foo.js, voglio davvero che lo script venga eseguito in due modi:

  1. ./foo.jsesegue lo script se uno nodeo nodejsè dentro $PATH.
  2. node foo.jsesegue anche lo script (supponendo che venga chiamato l'interprete node)

Nota: le risposte di xavierm02 e di me sono due varianti di uno script poliglotta. Sono ancora interessato a una soluzione pura di shebang, se esiste.


Penso che non ci sia una vera soluzione a questo, in quanto si può nominare arbitrariamente l'eseguibile dai sistemi di compilazione. Non c'è nulla che ti impedisca di nominare l'interprete python alphacentauri, basta seguire le convenzioni e chiamarlo python. Suggerirei di utilizzare il nome standard nodeper il tuo script o di avere una sorta di script di make che modifica lo shebang.

@ G.Kayaalp Politica e convenzioni a parte, ci sono molti utenti Debian / Ubuntu / Fedora e voglio creare degli script che funzionino per loro. Non voglio impostare un sistema di compilazione per questo (chi costruisce gli script di shell prima di eseguirli?), Né voglio supportare alphacentaurie così via. Se c'è un eseguibile chiamato nodejs, puoi essere sicuro al 99% che sia Node.js. Perché non supportare entrambi nodejse node?
dancek,

Installa il pacchetto nodejs-legacy. La ragione per cui ciò è necessario è che il nome è troppo arrogantemente generico, qualcun altro lo ha prima ottenuto. L'altro pacchetto era tuttavia disposto a condividere il nome.
user3710044,

Risposte:


54

Il migliore che ho trovato è questo "shebang a due righe" che in realtà è uno script poliglotta (Bourne shell / Node.js):

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

console.log('Hello world!');

La prima linea è, ovviamente, uno shebang conchiglia Bourne. Node.js ignora qualsiasi shebang che trova, quindi questo è un file javascript valido per quanto riguarda Node.js.

La seconda riga chiama la shell no-op :con l'argomento //e quindi esegue nodejso nodecon il nome di questo file come parametro. command -vviene utilizzato al posto della whichportabilità. La sintassi di sostituzione dei comandi $(...)non è strettamente Bourne, quindi optare per i backtick se lo si esegue negli anni '80.

Node.js valuta solo la stringa ':', che è come una no-op, e il resto della riga viene analizzato come un commento.

Il resto del file è semplicemente javascript vecchio. La subshell si chiude al termine della execseconda riga, quindi il resto del file non viene mai letto dalla shell.

Grazie a xavierm02 per l'ispirazione e tutti i commentatori per ulteriori informazioni!


1
Ottima soluzione Un'alternativa ':'all'approccio è usare // 2>/dev/null(che è quello che nvmfa): bash, è un errore ( bash: //: Is a directory), che il reindirizzamento 2>/dev/nullignora tranquillamente; in JavaScript, l'intera riga diventa un commento. Inoltre - e non mi aspetto che questo sia un problema IRL - commandha una stranezza in cui riporta anche funzioni di shell e alias con -v, anche se in realtà non li invocherebbe. Quindi, se ti capita di avere esportato funzioni shell o anche alias (supponendo shopt -s expand_aliases) nominati nodeo nodejs, le cose potrebbero rompersi.
mklement0

1
Bene, POSIX sh è onnipresente in questi giorni (tranne Solaris / bin / sh - ma Solaris viene fornito con AT&T ksh out of the box, che è compatibile con POSIX), e Markus Kuhn consiglia (con giustificazione) di starne alla larga. IMHO non ne hai mai bisogno. Ma sì, è abbastanza per mksh, bash, dashe altre conchiglie in moderni sistemi Unix e GNU.
mirabilos,

1
Ottima soluzione; anche se sarebbe ancora più portatile da usare #!/usr/bin/env shinvece di #!/bin/sh(per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089

2
Starei attento alla pubblicità #!/usr/bin/env shcome "più portatile". Lo stesso articolo di Wikipedia dice "Funziona principalmente perché il percorso / usr / bin / env è comunemente usato per l'utilità env ..." Questo non è un supporto meraviglioso, e la mia ipotesi è che ti imbatterai in sistemi senza /usr/bin/envpiù frequentemente di ti imbatti in sistemi senza /bin/sh, se c'è qualche differenza di frequenza.
sheldonh,

1
@dancek Ciao dal 2018, non sarebbe //bin/sh -c :; exec ...una versione più pulita della seconda linea?
Har-Wradim,

10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseè la stessa cosa, /bin/falsetranne per il fatto che la seconda barra lo trasforma in un commento per nodo, ed è per questo che è qui. Quindi, ||viene valutato il lato destro del primo . 'which node || which nodejs'con backquotes invece di virgolette avvia il nodo e lo <<alimenta qualunque sia sulla destra. Avrei potuto usare un delimitatore a partire da //come ha fatto dancek, avrebbe funzionato ma trovo più pulito avere solo due righe all'inizio, quindi ero solito tail -n +2 $0leggere il file da solo tranne le prime due righe.

E se lo esegui nel nodo, la prima riga viene riconosciuta come shebang e ignorata e la seconda è un commento di una riga.

(Apparentemente, sed potrebbe essere usato per sostituire il contenuto del file di stampa di coda senza la prima e l'ultima riga )


Rispondi prima della modifica:

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

Non puoi fare quello che vuoi, quindi quello che fai invece è eseguire uno script di shell, da cui il #!/bin/sh. Lo script della shell otterrà il percorso del file necessario per eseguire il nodo, ovvero which node || which nodejs. I backquotes sono qui in modo che vengano eseguiti, quindi 'which node || which nodejs'(con i backquotes invece delle virgolette) chiama semplicemente il nodo. Quindi, basta alimentare il tuo script con esso <<. Il __HERE__sono i delimitatori di scrittura vostro. E console.log('ok');c'è un esempio di script che dovresti sostituire con il tuo script.


una spiegazione sarebbe buona
0xC0000022L

Meglio citare il delimitatore qui-documento per evitare di espansione di parametro, sostituzione di comando ed espansione aritmetica da eseguire sul codice JavaScript prima dell'esecuzione: <<'__HERE__'.
arte

Questo sta diventando interessante! Anche //bin/falsese non funziona nel mio ambiente MSYS, e ho bisogno di virgolette attorno ai backtick come il mio noderisiede a C:\Program Files\.... Sì, lavoro in un ambiente orribile ...
dancek,

1
//bin/falsenon funziona neanche su Mac OS X. Mi dispiace, ma questo non sembra più molto portatile.
dancek,

2
Inoltre, il whichcomando non è portatile.
Giordania,

9

Questo è solo un problema sui sistemi basati su Debian, dove la politica ha superato il senso.

Non so quando Fedora fornì un binario chiamato nodejs, ma non l'ho mai visto. Il pacchetto si chiama nodejs e installa un binario chiamato nodo.

Usa semplicemente un link simbolico per applicare il buon senso ai tuoi sistemi basati su Debian, e quindi puoi usare un shebang sano di mente. Altre persone useranno comunque sane shebang, quindi avrai bisogno di quel link simbolico.

#!/usr/bin/env node

console.log("Spread the love.");

Mi dispiace, ma questo non risponde alla domanda. Capisco il punto di vista politico, ma non è questo il punto.
dancek,

Per la cronaca, alcuni sistemi Fedora hanno un nodejseseguibile , ma non è colpa di Fedora. Ci scusiamo per aver travisato il fatto.
dancek,

8
Non c'è bisogno di scusarsi. Hai ragione. La mia risposta non risponde alla tua domanda. Parla alla tua domanda, suggerendo che la domanda limita la portata a soluzioni meno utili di quelle disponibili. Offro consigli pratici informati dalla storia. Nodo non è il primo interprete a trovarsi qui. Avresti dovuto vedere la confusione che ha portato Larry Wall a dichiarare che il PERL shebang DEVE essere #!/usr/bin/perl. Detto questo, non sei obbligato ad apprezzare o applicare i miei suggerimenti. Pace.
sheldonh,

3

Se non ti dispiace creare un piccolo .shfile, ho una piccola soluzione per te. Puoi creare un piccolo script shell per determinare quale nodo eseguibile usare e usare questo script nel tuo shebang:

shebang.sh :

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

script.js :

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

Contrassegna sia eseguibile sia esegui ./script.js.

In questo modo, si evitano gli script poliglotta. Non credo sia possibile utilizzare più linee di shebang, anche se sembra una buona idea.

Anche se questo risolve il problema nel modo desiderato, a nessuno importa di questo. Ad esempio, uglifyjs e coffeescript usano #!/usr/bin/env node, npm usa uno script shell come punto di ingresso, che chiama di nuovo l'eseguibile esplicitamente con il nome node. Sono un utente Ubuntu e non lo sapevo poiché compilo sempre il nodo. Sto considerando di segnalarlo come un bug.


Sono abbastanza sicuro che dovresti almeno chiedere all'utente di scrivere chmod +xnello script sh ... quindi potresti anche chiedere loro di impostare una variabile che dia la posizione all'eseguibile del loro nodo ...
xavierm02

@ xavierm02 Uno può rilasciare lo script chmod +x'd. E concordo sul fatto che una variabile NODE sia migliore di which node || which nodejs. Ma il ricercatore desidera fornire un'esperienza immediata, anche se molti dei principali progetti di nodi vengono semplicemente utilizzati #!/usr/bin/env node.

1

Solo per completezza qui ci sono un paio di altri modi di fare la seconda riga:

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

Ma nessuno dei due ha alcun vantaggio rispetto alla risposta selezionata:

':' //; || echo yes

Inoltre, se sapessi che uno nodeo nodejs(ma non entrambi) sarebbero stati trovati, i seguenti lavori:

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

Ma questo è un grande "se", quindi penso che la risposta selezionata rimanga la migliore.


Inoltre, puoi eseguirlo foobar // 2>/dev/nulla condizione che foobarnon sia un comando. E molte delle utilità trovate su qualsiasi sistema POSIX possono essere eseguite con l' //argomento.
dancek,

1

Comprendo che ciò non risponde alla domanda, ma sono convinto che la domanda sia posta con una premessa errata.

Ubuntu ha torto qui. Scrivere uno shebang universale per il tuo script non cambierà altri pacchetti sui quali non hai alcun controllo su cui utilizzare lo standard di fatto #!/usr/bin/env node. Il sistema deve fornire nodein PATHse vuole per gli script di targeting nodejs per eseguire su di esso.

Ad esempio, nemmeno il npmpacchetto fornito da Ubuntu riscrive shebang nei pacchetti:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- mkdirp@0.5.1
  `-- minimist@0.0.8

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.