Universal Node.js shebang?


42

Node.js jest obecnie bardzo popularny i pisałem na nim kilka skryptów. Niestety kompatybilność stanowi problem. Oficjalnie należy wywoływać interpreter Node.js node, ale Debian i Ubuntu dostarczają nodejszamiast niego plik wykonywalny .

Chcę przenośnych skryptów, z którymi Node.js może pracować w jak największej liczbie sytuacji. Zakładając, że nazwa pliku brzmi foo.js, naprawdę chcę, aby skrypt działał na dwa sposoby:

  1. ./foo.jsuruchamia skrypt, czy też nodeczy nodejsjest $PATH.
  2. node foo.jsuruchamia również skrypt (zakładając, że wywoływany jest interpreter node)

Uwaga: Odpowiedzi xavierm02 i mnie to dwie odmiany skryptu polyglot. Nadal interesuje mnie rozwiązanie typu shebang, jeśli takie istnieje.


Myślę, że nie ma na to realnego rozwiązania, ponieważ system kompilacji może nazwać plik wykonywalny w sposób arbitralny. Nic nie stoi na przeszkodzie, aby nazwać interpreter języka pytającego alphacentauri, wystarczy przestrzegać konwencji i nazwać go python. Sugerowałbym użycie standardowej nazwy nodeskryptu lub stworzenie skryptu make, który modyfikuje shebang.

@ G.Kayaalp Poza politykami i konwencjami, jest wielu użytkowników Debiana / Ubuntu / Fedory i chcę stworzyć dla nich skrypty. Nie chcę konfigurować do tego systemu kompilacji (ktokolwiek buduje skrypty powłoki przed ich uruchomieniem?), Ani nie chcę obsługiwać alphacentaurii tak dalej. Jeśli istnieje plik wykonywalny o nazwie nodejs, możesz być w 99% pewien, że jest to plik Node.js. Dlaczego nie wspierać zarówno nodejsi node?
dancek

Zainstaluj pakiet nodejs-legacy. Jest to konieczne, ponieważ nazwa jest zbyt arogancko ogólna, ktoś inny otrzymał ją jako pierwszy. Ten drugi pakiet był jednak skłonny udostępnić nazwę.
user3710044

Odpowiedzi:


54

Najlepsze, co wymyśliłem, to ten „dwuwierszowy shebang”, który tak naprawdę jest skryptem polyglot (powłoka Bourne'a / Node.js):

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

console.log('Hello world!');

Pierwszą linią jest oczywiście szarpnięcie muszli Bourne'a. Node.js pomija każdy znaleziony shebang, więc jest to prawidłowy plik javascript, jeśli chodzi o Node.js.

Drugi wiersz wywołuje brak powłoki :z argumentem, //a następnie wykonuje nodejslub nodez nazwą tego pliku jako parametrem. command -vsłuży whichdo przenoszenia. Składnia podstawiania poleceń $(...)nie jest ściśle Bourne, więc wybierz backsticks, jeśli uruchomisz to w latach 80.

Node.js po prostu ocenia ciąg znaków ':', który jest jak no-op, a reszta linii jest analizowana jako komentarz.

Reszta pliku to po prostu stary javascript. Podkładka kończy pracę po zakończeniu execdrugiej linii, więc reszta pliku nigdy nie jest odczytywana przez powłokę.

Dzięki xavierm02 za inspirację i wszystkim komentującym za dodatkowe informacje!


1
Świetne rozwiązanie. Alternatywą dla tego ':'podejścia jest użycie // 2>/dev/null(co nvmrobi): bash, to błąd ( bash: //: Is a directory), który przekierowanie 2>/dev/nullcicho ignoruje; do JavaScript cała linia staje się komentarzem. Ponadto - i nie spodziewam się, że będzie to problem IRL - commandma dziwactwo, w którym zgłasza również funkcje powłoki i aliasy -v- nawet jeśli tak naprawdę ich nie wywołał. Tak więc, jeśli zdarzyło Ci się wyeksportować funkcje powłoki lub nawet aliasy (zakładając shopt -s expand_aliases) o nazwie nodelub nodejs, rzeczy mogą się zepsuć.
mklement0

1
No cóż, POSIX sh jest obecnie wszechobecny (oprócz Solaris / bin / sh - ale Solaris jest dostarczany z AT&T ksh po wyjęciu z pudełka, który jest kompatybilny z POSIX), a Markus Kuhn zaleca (z uzasadnieniem), aby się z niego wyłączał. IMHO, nigdy tego nie potrzebujesz. Ale tak, to wystarcza mksh, bash, dashi innych skorup w nowoczesnych systemach Unix i GNU.
mirabilos

1
Świetne rozwiązanie; chociaż nawet bardziej przenośne byłoby użyć #!/usr/bin/env shzamiast #!/bin/sh(na en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089

2
Byłbym ostrożny z reklamami #!/usr/bin/env shjako „bardziej przenośnymi”. Ten sam artykuł w Wikipedii mówi: „Działa to głównie dlatego, że ścieżka / usr / bin / env jest powszechnie używana w narzędziu env ...” To nie jest wspaniałe poparcie, i przypuszczam, że będziesz wpadał na systemy /usr/bin/envczęściej niż wpadasz na systemy bez /bin/sh, jeśli w ogóle występuje jakakolwiek różnica częstotliwości.
sheldonh

1
@dancek Cześć od 2018 roku, czy nie byłaby //bin/sh -c :; exec ...to czystsza wersja drugiej linii?
har-wradim

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

//bin/falsejest tym samym, /bin/falsez wyjątkiem tego, że drugi ukośnik przekształca go w komentarz do węzła i dlatego jest tutaj. Następnie ||oceniana jest prawa strona pierwszego . 'which node || which nodejs'z cudzysłowami zamiast cudzysłowów uruchamia węzeł i <<przekazuje go, co jest po prawej stronie. Mógłbym użyć ogranicznika zaczynającego się //tak, jak zrobił to dancek, zadziałałoby, ale uważam, że czystsze jest posiadanie tylko dwóch linii na początku, więc miałem tail -n +2 $0plik czytany sam, z wyjątkiem dwóch pierwszych linii.

A jeśli uruchomisz go w węźle, pierwszy wiersz zostanie rozpoznany jako shebang i zignorowany, a drugi to komentarz jednowierszowy.

(Najwyraźniej sed może być użyty do zastąpienia zawartości pliku wydruku ogona bez pierwszej i ostatniej linii )


Odpowiedz przed edycją:

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

Nie możesz robić tego, co chcesz, więc zamiast tego uruchom skrypt powłoki, stąd #!/bin/sh. Ten skrypt powłoki uzyska ścieżkę do pliku potrzebnego do uruchomienia węzła, to znaczy which node || which nodejs. Cytaty wsteczne są tutaj, aby zostały wykonane, więc 'which node || which nodejs'(z cudzysłowami zamiast cudzysłowów) po prostu wywołuje węzeł. Następnie po prostu nakarm go swoim skryptem <<. Są __HERE__to ograniczniki twojego skryptu. A console.log('ok');to przykład skryptu, który należy wymienić ze skryptu.


wyjaśnienie byłoby miłe
0xC0000022L

Lepiej zacytować tu separator dokument interpretacji parametrów uniknąć podstawiania poleceń i interpretacji wyrażeń arytmetycznych, które będą wykonywane na wykonanie kodu JavaScript przed: <<'__HERE__'.
manatwork

To robi się ciekawe! Chociaż //bin/falsenie działa w moim środowisku MSYS i potrzebuję cudzysłowów za backtickami w miejscu nodezamieszkania C:\Program Files\.... Tak, pracuję w okropnym środowisku ...
dancek

1
//bin/falsenie działa również w systemie Mac OS X. Przepraszam, ale to już nie wydaje się zbyt przenośne.
dancek

2
whichPolecenie nie jest przenośny.
jordanm

9

Jest to tylko problem w systemach opartych na Debianie, w których zasady zostały przezwyciężone.

Nie wiem, kiedy Fedora dostarczył plik binarny o nazwie nodejs, ale nigdy go nie widziałem. Pakiet nazywa się nodejs i instaluje binarny węzeł.

Po prostu użyj dowiązania symbolicznego, aby zastosować zdrowy rozsądek w systemach opartych na Debianie, a następnie możesz użyć zdrowego rozsądku. Zresztą inne osoby i tak będą używać zdrowych szejków, więc będziesz potrzebować tego dowiązania symbolicznego.

#!/usr/bin/env node

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

Przepraszam, ale to nie odpowiada na pytanie. Rozumiem polityczny punkt widzenia, ale nie o to tutaj chodzi.
dancek

Dla przypomnienia, niektóre systemy Fedory mają nodejsplik wykonywalny , ale to nie wina Fedory. Przepraszam za wprowadzenie w błąd.
dancek

8
Nie trzeba przepraszać. Masz w zasadzie rację. Moja odpowiedź nie odpowiada na twoje pytanie. Odpowiada na twoje pytanie, sugerując, że pytanie ogranicza zakres do mniej użytecznych rozwiązań niż są dostępne. Oferuję praktyczne porady oparte na historii. Węzeł nie jest pierwszym tłumaczem, który się tu znalazł. Powinieneś był zobaczyć zamieszanie, które doprowadziło Larry'ego Walla do ogłoszenia, że ​​perl shebang MUSI być #!/usr/bin/perl. To powiedziawszy, nie jesteś zobowiązany do polubienia lub zastosowania moich sugestii. Pokój.
sheldonh

3

Jeśli nie masz nic przeciwko utworzeniu małego .shpliku, mam dla ciebie małe rozwiązanie. Możesz utworzyć mały skrypt powłoki, aby określić, który węzeł ma być wykonywalny, i użyć tego skryptu w swoim shebang:

shebang.sh :

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

skrypt.js :

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

Zaznacz oba pliki wykonywalne i uruchom ./script.js.

W ten sposób unikasz skryptów polyglot. Nie sądzę, aby stosowanie wielu linii shebang było możliwe, chociaż wydaje się to dobrym pomysłem.

Chociaż rozwiązuje to problem tak, jak chcesz, wydaje się, że nikogo to nie obchodzi. Na przykład, uglifyjs i coffeescript zastosowań #!/usr/bin/env node, npm wykorzystuje skrypt jako punkt wyjścia, który ponownie wywołuje wyraźnie z nazwy pliku wykonywalnego node. Jestem użytkownikiem Ubuntu i nie wiedziałem o tym, ponieważ zawsze kompiluję węzeł. Rozważam zgłoszenie tego jako błędu.


Jestem pewien, że przynajmniej będziesz musiał poprosić użytkownika chmod +xo skrypt sh ... więc równie dobrze możesz poprosić go o ustawienie zmiennej podającej lokalizację wykonywalnego węzła ...
xavierm02

@ xavierm02 Można zwolnić skrypt chmod +x„d. Zgadzam się, że zmienna NODE jest lepsza niż which node || which nodejs. Ale pytający chce zapewnić gotową obsługę, chociaż korzysta z niej wiele dużych projektów węzłów #!/usr/bin/env node.

1

Dla kompletności oto kilka innych sposobów wykonania drugiej linii:

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

Ale żadna nie ma żadnej przewagi nad wybraną odpowiedzią:

':' //; || echo yes

Ponadto, jeśli wiesz, że zostanie znaleziony jeden ( nodelub nodejsnie oba), następujące działania:

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

Ale to duże „jeśli”, więc myślę, że wybrana odpowiedź pozostaje najlepsza.


Dodatkowo możesz uruchomić dowolny, foobar // 2>/dev/nullo ile foobarnie jest to polecenie. Wiele narzędzi znalezionych w dowolnym systemie POSIX można uruchomić z //argumentem.
dancek

1

Rozumiem, że to nie odpowiada na pytanie, ale jestem przekonany, że pytanie zostało postawione w fałszywym założeniu.

Ubuntu się tutaj myli. Napisanie uniwersalnego shebang dla własnego skryptu nie zmieni innych pakietów, nad którymi nie masz kontroli, z których faktycznie korzysta standard #!/usr/bin/env node. System musi zapewnić nodew PATHjeśli chce żadnych skryptów kierowanych nodejs uruchomić na nim.

Na przykład nawet npmpakiet dostarczony przez Ubuntu nie przepisuje shebangów w pakietach:

$ 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"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.