Co dokładnie robi „/ usr / bin / env node” na początku plików węzłów?

109

Widziałem tę linię #!/usr/bin/env nodena początku niektórych przykładów w programie nodejsi przeszukałem go w Google, nie znajdując żadnego tematu, który mógłby odpowiedzieć na przyczynę tej linii.

Charakter słów sprawia, że ​​wyszukiwanie nie jest takie łatwe.

Czytałem trochę javascripti nodejsksiążek niedawno i nie pamiętam widząc go w żadnym z nich.

Jeśli potrzebujesz przykładu, możesz zobaczyć RabbitMQoficjalny samouczek , mają go w prawie wszystkich swoich przykładach, oto jeden z nich:

#!/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);
});

Czy ktoś mógłby mi wyjaśnić, jakie jest znaczenie tego wiersza?

Jaka jest różnica, jeśli wstawię lub usunę tę linię? W jakich przypadkach go potrzebuję?

Gepser
źródło
3
w zasadzie pobiera środowisko wywołującej powłoki i umieszcza je w dowolnej określonej aplikacji. w tym przypadkunode
Marc B
Właściwie nie, nie pochodzę z systemu Windows, ale dziękuję za zaktualizowanie odpowiedzi. Czekam tylko, żeby zobaczyć, czy ktoś inny ma inną opinię. Jest tylko jedna rzecz, o której myślę, że nie wspomniałaś w swojej odpowiedzi, po prostu znalazłem ją kilka godzin temu. Rzeczy, o których tutaj wspominają, wydają się być ważne, ale nie jest to dla mnie wystarczająco jasne. stackoverflow.com/questions/14517535/… (możesz zaktualizować, jeśli chcesz, naprawdę to docenię, ale nie czuję, że jest to zobowiązanie, twoja odpowiedź jest teraz wystarczająco dobra).
Gepser
@Gepser: Rozumiem. Krótko mówiąc: jeśli chcesz npmzainstalować skrypt źródłowy Node.js jako (potencjalnie dostępny globalnie) CLI , musisz użyć linii shebang - a npmnawet sprawisz, że będzie działać w systemie Windows; zobacz moją ponownie zaktualizowaną odpowiedź.
mklement0
„Ze względu na naturę słów wyszukiwanie nie jest takie łatwe” - możesz wypróbować witrynę duckduckgo.com dla tego konkretnego przypadku zastosowania wyszukiwania
Ricardo

Odpowiedzi:

146

#!/usr/bin/env nodeto przykład linii shebang : pierwsza linia w wykonywalnym pliku tekstowym na platformach uniksopodobnych, która informuje system, do jakiego interpretera ma przekazać ten plik do wykonania , za pośrednictwem wiersza poleceń następującego po magicznym #!przedrostku (nazywanym shebang ) .

Uwaga: System Windows nie nie obsługują shebang linii , więc są one skutecznie ignorowane tam; w systemie Windows tylko rozszerzenie nazwy pliku danego pliku określa, jaki plik wykonywalny go zinterpretuje. Jednak nadal potrzebujesz ich w kontekścienpm . [1]

Poniższe ogólne omówienie linii shebang jest ograniczone do platform typu Unix:

W poniższej dyskusji zakładam, że plik zawierający kod źródłowy do wykonania przez Node.js ma po prostu nazwę file.

  • Ty potrzebujesz tej linii , jeśli chcesz, aby wywołać plik źródłowy node.js bezpośrednio , jako plik wykonywalny w sobie - to zakłada się, że plik został oznaczony jako wykonywalny z polecenia, takie jak chmod +x ./file, co następnie pozwala wywołać plik na przykład, ./filelub, jeśli znajduje się w jednym z katalogów wymienionych w $PATHzmiennej, po prostu jako file.

    • W szczególności potrzebny jest wiersz shebang do tworzenia interfejsów wiersza poleceń opartych na plikach źródłowych Node.js jako części pakietu npm , z CLI (-ami) do zainstalowania na npmpodstawie wartości "bin"klucza w package.jsonpliku pakietu ; zobacz również tę odpowiedź, aby dowiedzieć się, jak to działa z pakietami zainstalowanymi globalnie . Przypis [1] pokazuje, jak jest to obsługiwane w systemie Windows.
  • Państwo nie trzeba tę linię, aby wywołać plik bezpośrednio poprzez nodetłumacza, na przykład,node ./file


Opcjonalne informacje dodatkowe :

#!/usr/bin/env <executableName>jest sposobem przenośnego określenia interpretera: w skrócie mówi: wykonaj, <executableName>gdziekolwiek (najpierw) znajdziesz go wśród katalogów wymienionych w $PATHzmiennej (i niejawnie przekaż mu ścieżkę do dostępnego pliku).

Wynika to z faktu, że dany interpreter może być zainstalowany w różnych lokalizacjach na różnych platformach, co z pewnością ma miejsce w przypadku nodepliku binarnego Node.js.

Z drugiej strony envmożna polegać na tym, że lokalizacja samego narzędzia znajduje się w tej samej lokalizacji na różnych platformach, a mianowicie /usr/bin/env- a określenie pełnej ścieżki do pliku wykonywalnego jest wymagane w wierszu shebang.

Zwróć uwagę, że narzędzie POSIX envjest tu ponownie wykorzystywane do lokalizowania według nazwy pliku i wykonywania pliku wykonywalnego w $PATH.
Prawdziwym celem envjest zarządzanie środowiskiem dla polecenia - zobacz envspecyfikację POSIX i pomocną odpowiedź Keitha Thompsona .


Warto również zauważyć, że Node.js robi wyjątek składniowy dla linii shebang, biorąc pod uwagę, że nie są one prawidłowym kodem JavaScript ( #nie jest znakiem komentarza w JavaScript, w przeciwieństwie do powłok podobnych do POSIX i innych interpreterów).


[1] W celu zachowania spójności między platformami, npmtworzy pliki opakowujące *.cmd (pliki wsadowe) w systemie Windows podczas instalowania plików wykonywalnych określonych w package.jsonpliku pakietu (za pośrednictwem "bin"właściwości). Zasadniczo te pliki wsadowe opakowania naśladują funkcjonalność uniksowego shebang: wywołują plik docelowy jawnie za pomocą pliku wykonywalnego określonego w wierszu shebang - w związku z tym skrypty muszą zawierać wiersz shebang, nawet jeśli zamierzasz je uruchamiać tylko w systemie Windows - zobacz tę odpowiedź o szczegóły.
Ponieważ *.cmdpliki można wywoływać bez rozszerzenia.cmdrozszerzenie, zapewnia to bezproblemowe działanie na wielu platformach: zarówno w systemie Windows, jak i Unix można skutecznie wywołać npmzainstalowany interfejs CLI za pomocą jego oryginalnej nazwy bez rozszerzeń.

mklement0
źródło
Czy możesz podać wyjaśnienie lub podsumowanie dla manekinów takich jak ja?
Andrew Lam
4
@AndrewLam: W systemie Windows rozszerzenia nazw plików, takie jak .cmdi .pyokreśl, jaki program będzie używany do wykonywania takich plików. W systemie Unix funkcję tę pełni linia shebang. Aby npmdziałać na wszystkich obsługiwanych platformach, potrzebujesz linii shebang nawet w systemie Windows.
mklement0
28

Skrypty, które mają być wykonane przez tłumacza, zwykle mają linię szarpnięcia u góry która informuje system operacyjny, jak je wykonać.

Jeśli masz skrypt o nazwie, fooktórego pierwsza linia to #!/bin/sh, system odczyta tę pierwszą linię i wykona odpowiednik /bin/sh foo. Z tego powodu większość interpreterów jest skonfigurowana tak, aby akceptować nazwę pliku skryptu jako argument wiersza poleceń.

Nazwa tłumacza występująca po #! musi być pełną ścieżką; system operacyjny nie będzie szukał twojego $PATHtłumacza.

Jeśli masz skrypt do wykonania node, oczywistym sposobem napisania pierwszej linii jest:

#!/usr/bin/node

ale to nie działa, jeśli nodepolecenie nie jest zainstalowane w/usr/bin .

Typowym obejściem jest użycie envpolecenia (które tak naprawdę nie było przeznaczone do tego celu):

#!/usr/bin/env node

Jeśli Twój skrypt zostanie wywołany foo, system operacyjny wykona odpowiednik

/usr/bin/env node foo

envPolecenie wykonuje inną komendę, którego nazwa podana jest w linii poleceń, przechodząc żadnych następujące argumenty do tego polecenia. Powodem jest to, że envwyszuka $PATHpolecenie. Więc jeśli nodejest zainstalowany /usr/local/bin/nodei masz /usr/local/binw swoim $PATH, envpolecenie zostanie wywołane /usr/local/bin/node foo.

Głównym celem envpolecenia jest wykonanie innego polecenia w zmodyfikowanym środowisku, dodanie lub usunięcie określonych zmiennych środowiskowych przed uruchomieniem polecenia. Ale bez dodatkowych argumentów, po prostu wykonuje polecenie w niezmienionym środowisku, co jest wszystkim, czego potrzebujesz w tym przypadku.

Takie podejście ma pewne wady. Większość nowoczesnych systemów typu Unix ma /usr/bin/env, ale pracowałem na starszych systemach, w których envpolecenie zostało zainstalowane w innym katalogu. Mogą istnieć ograniczenia dotyczące dodatkowych argumentów, które można przekazać za pomocą tego mechanizmu. Jeśli użytkownik nie ma katalogu zawierającego nodepolecenie $PATHlub ma inną komendę o nazwienode , może wywołać niewłaściwe polecenie lub w ogóle nie działać.

Inne podejścia to:

  • Użyj #!wiersza określającego pełną ścieżkę do samego nodepolecenia, aktualizując skrypt zgodnie z potrzebami dla różnych systemów; lub
  • Wywołaj nodepolecenie ze swoim skryptem jako argumentem.

Zobacz także to pytanie (i moją odpowiedź ), aby uzyskać więcej informacji na temat#!/usr/bin/env sztuczce.

Nawiasem mówiąc, w moim systemie (Linux Mint 17.2) jest zainstalowany jako /usr/bin/nodejs. Według moich notatek zmieniło się z /usr/bin/nodena /usr/bin/nodejsmiędzy Ubuntu 12.04 a 12.10. Ta #!/usr/bin/envsztuczka nie pomoże w tym (chyba że skonfigurujesz łącze symboliczne lub coś podobnego).

AKTUALIZACJA: komentarz mtraceur mówi (przeformatowano):

Obejście problemu nodejs vs węzeł polega na uruchomieniu pliku z następującymi sześcioma wierszami:

#!/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
*/

To najpierw spróbuje, nodejsa następnie spróbuje nodei wydrukuje komunikaty o błędach tylko wtedy, gdy oba z nich nie zostaną znalezione. Wyjaśnienie jest poza zakresem tych komentarzy, zostawiam je tutaj na wypadek, gdyby pomogło komukolwiek poradzić sobie z problemem, ponieważ ta odpowiedź przyniosła problem.

Ostatnio nie korzystałem z NodeJS. Mam nadzieję, że problem nodejsz nodewersją został rozwiązany w latach, które minęły, odkąd po raz pierwszy opublikowałem tę odpowiedź. W systemie Ubuntu 18.04 nodejspakiet instaluje się /usr/bin/nodejsjako łącze symboliczne do /usr/bin/node. W niektórych wcześniejszych systemach operacyjnych (Ubuntu lub Linux Mint, nie jestem pewien który) istniał nodejs-legacypakiet, który był dostarczany nodejako dowiązanie symboliczne do nodejs. Nie ma gwarancji, że wszystkie szczegóły są prawidłowe.

Keith Thompson
źródło
Bardzo dokładna odpowiedź, podająca przyczyny.
Suraj Jain
1
Obejście problemu nodejs vs nodepolega na uruchomieniu pliku z następującymi sześcioma wierszami: 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) */. To najpierw spróbuje, nodejsa następnie spróbuje nodei wydrukuje komunikaty o błędach tylko wtedy, gdy oba z nich nie zostaną znalezione. Wyjaśnienie jest poza zakresem tych komentarzy, zostawiam je tutaj na wypadek, gdyby pomogło komukolwiek poradzić sobie z problemem, ponieważ ta odpowiedź przyniosła problem.
mtraceur
@mtraceur: Włączyłem Twój komentarz do mojej odpowiedzi. Dlaczego -na #!linii?
Keith Thompson
-W #!/bin/sh -to tylko przyzwyczajenie sprawia, że na pewno się zachowuje się powłoki w samym bardzo wąski i mało prawdopodobnym zestaw okoliczności, że nazwa skryptu lub względna ścieżka że powłoka zaczyna się widzi -. (Ponadto, tak, wygląda na to, że każda dystrybucja głównego nurtu powróciła do nodenazwy głównej. Nie szukałem w komentarzu, ale z tego, co wiem, używam tylko drzewa genealogicznego dystrybucji Debiana nodejsi wygląda na to, że tak jak wszyscy wrócili do wspierania, nodegdy zrobił to Debian.)
mtraceur
Technicznie rzecz biorąc, pojedynczy myślnik jako pierwszy argument nie oznaczał „końca opcji” - pierwotnie oznaczał „wyłącz -xi -v”, ale ponieważ wczesne polubienia Bourne'a analizowały tylko pierwszy argument jako możliwe opcje, a ponieważ powłoka zaczyna się z wyłączonymi opcjami , powodowanie, że powłoka nie próbowała analizować nazwy skryptu od czasu wydania oryginału, było nadużyciem, ponieważ zachowanie jest utrzymywane we współczesnych polubieniach Bourne'a ze względu na kompatybilność. Jeśli dobrze pamiętam całą moją historię Bourne'a i ciekawostki dotyczące przenośności.
mtraceur
0

Krótka odpowiedź: To droga do tłumacza.

EDYCJA (długa odpowiedź): Przyczyną braku ukośnika przed „węzłem” jest to, że nie zawsze można zagwarantować niezawodność #! / Bin /. Bit „/ env” sprawia, że ​​program jest bardziej wieloplatformowy, uruchamiając skrypt w zmodyfikowanym środowisku i bardziej niezawodnie będąc w stanie znaleźć program interpretujący.

Niekoniecznie jest to potrzebne, ale dobrze jest używać, aby zapewnić przenośność (i profesjonalizm)

Kwant
źródło
1
/usr/bin/envNieco nie zmienia środowisko. Jest to po prostu polecenie w (głównie) znanej lokalizacji, które wywołuje inne polecenie podane jako argument i wyszukuje $PATHje. Chodzi o to, że #!linia wymaga pełnej ścieżki do wywoływanego polecenia i niekoniecznie wiesz, gdzie nodejest zainstalowane.
Keith Thompson
Właśnie o to mi chodziło, dzięki za wyjaśnienie!
Quantum