¿Qué hace exactamente “/ usr / bin / env node” al principio de los archivos de nodo?

109

Había visto esta línea #!/usr/bin/env nodeal principio de algunos ejemplos en nodejsy busqué en Google sin encontrar ningún tema que pudiera responder el motivo de esa línea.

La naturaleza de las palabras hace que la búsqueda no sea tan fácil.

Leí algunos libros javascripty nodejsrecientemente y no recordaba haberlos visto en ninguno de ellos.

Si quieres un ejemplo, puedes ver el tutorialRabbitMQ oficial , lo tienen en casi todos sus ejemplos, aquí tienes uno de ellos:

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

¿Alguien podría explicarme cuál es el significado de esta línea?

¿Cuál es la diferencia si pongo o quito esta línea? ¿En que casos lo necesito?

Gepser
fuente
3
Básicamente, toma el entorno del shell de llamada y lo integra en cualquier aplicación que se especifique. en este caso,node
Marc B
En realidad no, no vengo de Windows, pero gracias por actualizar su respuesta. Solo estoy esperando a ver si alguien más tiene una opinión diferente. Solo hay una cosa que creo que no mencionaste en tu respuesta, la encontré hace un par de horas. Las cosas que mencionan aquí parecen algo importantes, pero todavía no están lo suficientemente claras para mí. stackoverflow.com/questions/14517535/… (puede actualizar si lo desea, realmente lo agradeceré, pero no lo sienta como una obligación, su respuesta es lo suficientemente buena en este momento).
Gepser
@Gepser: Entendido. En resumen: si desea npminstalar un script fuente de Node.js como una CLI (potencialmente disponible a nivel mundial) , debe usar una línea shebang, e npmincluso hará que funcione en Windows; vea mi respuesta actualizada una vez más.
mklement0
"La naturaleza de las palabras hace que la búsqueda no sea tan fácil" . Quizás desee probar con duckduckgo.com para este caso de uso de búsqueda específico
Ricardo

Respuestas:

146

#!/usr/bin/env nodees una instancia de una línea shebang : la primera línea en un archivo ejecutable de texto plano en plataformas similares a Unix que le dice al sistema a qué intérprete pasar ese archivo para su ejecución , a través de la línea de comando que sigue al #!prefijo mágico (llamado shebang ) .

Nota: en Windows no no admite shebang líneas , por lo que están efectivamente ignorados allí; en Windows, es únicamente la extensión del nombre de archivo de un archivo determinado lo que determina qué ejecutable lo interpretará. Sin embargo, aún los necesita en el contexto denpm . [1]

La siguiente discusión general de las líneas shebang se limita a plataformas similares a Unix:

En la siguiente discusión asumiré que el archivo que contiene el código fuente para ser ejecutado por Node.js simplemente se nombra file.

  • Usted NECESITA esta línea , si desea invocar un archivo de origen Node.js directamente , como un archivo ejecutable en su propio derecho - esto supone que el archivo se ha marcado como ejecutable con un comando como chmod +x ./file, que a su vez le permite invocar el archivo con, por ejemplo,, ./fileo, si está ubicado en uno de los directorios listados en la $PATHvariable, simplemente como file.

    • Específicamente, necesita una línea shebang para crear CLI basadas en archivos fuente de Node.js como parte de un paquete npm , con las CLI que se instalarán npmsegún el valor de la "bin"clave en el package.jsonarchivo de un paquete ; también vea esta respuesta para saber cómo funciona con paquetes instalados globalmente . La nota al pie [1] muestra cómo se maneja esto en Windows.
  • Usted NO necesita esta línea para invocar un archivo de forma explícita a través del nodeintérprete, por ejemplo,node ./file


Información de antecedentes opcional :

#!/usr/bin/env <executableName>es una forma de especificar un intérprete de forma portátil : en pocas palabras, dice: ejecutar <executableName>donde (primero) lo encuentre entre los directorios enumerados en la $PATHvariable (y pasarle implícitamente la ruta al archivo en cuestión).

Esto explica el hecho de que un intérprete determinado puede instalarse en diferentes ubicaciones en todas las plataformas, lo que definitivamente es el caso nodedel binario Node.js.

Por el contrario, envse puede confiar en que la ubicación de la utilidad en sí está en la misma ubicación en todas las plataformas, es decir /usr/bin/env, y se requiere especificar la ruta completa a un ejecutable en una línea shebang.

Tenga en cuenta que la utilidad POSIX envse está reutilizando aquí para ubicarla por nombre de archivo y ejecutar un ejecutable en formato $PATH.
El verdadero propósito de enves administrar el entorno para un comando; consulte envla especificación POSIX y la útil respuesta de Keith Thompson .


También vale la pena señalar que Node.js está haciendo una excepción de sintaxis para las líneas shebang, dado que no son un código JavaScript válido ( #no es un carácter de comentario en JavaScript, a diferencia de las shells similares a POSIX y otros intérpretes).


[1] En aras de la coherencia entre plataformas, npmcrea archivos de envoltura *.cmd (archivos por lotes) en Windows al instalar ejecutables especificados en el package.jsonarchivo de un paquete (a través de la "bin"propiedad). Esencialmente, estos archivos por lotes de contenedor imitan la funcionalidad shebang de Unix: invocan el archivo de destino explícitamente con el ejecutable especificado en la línea shebang ; por lo tanto, sus scripts deben incluir una línea shebang incluso si solo tiene la intención de ejecutarlos en Windows ; vea esta respuesta mía para más detalles.
Dado que los *.cmdarchivos se pueden invocar sin la.cmdextensión, esto lo convierte en una experiencia multiplataforma perfecta: tanto en Windows como en Unix, puede invocar efectivamente una npmCLI instalada por su nombre original sin extensión.

mklement0
fuente
¿Puede proporcionar una explicación o un resumen para tontos como yo?
Andrew Lam
4
@AndrewLam: en Windows, las extensiones de nombre de archivo como .cmdy .pydeterminan qué programa se utilizará para ejecutar dichos archivos. En Unix, la línea shebang realiza esa función. Para que npmfuncione en todas las plataformas compatibles, necesita la línea shebang incluso en Windows.
mklement0
28

Los scripts que deben ser ejecutados por un intérprete normalmente tienen una línea shebang en la parte superior para indicarle al sistema operativo cómo ejecutarlos.

Si tiene un script llamado foo cuyo primera línea #!/bin/sh, el sistema leerá esa primera línea y ejecutará el equivalente de /bin/sh foo. Debido a esto, la mayoría de los intérpretes están configurados para aceptar el nombre de un archivo de secuencia de comandos como argumento de línea de comandos.

El nombre del intérprete que sigue al #! a debe ser una ruta completa; el sistema operativo no buscará su $PATHintérprete.

Si tiene un script para que lo ejecute node , la forma obvia de escribir la primera línea es:

#!/usr/bin/node

pero eso no funciona si el node comando no está instalado en /usr/bin.

Una solución alternativa común es usar el envcomando (que no era realmente destinado a este propósito):

#!/usr/bin/env node

Si se llama a su script foo, el sistema operativo hará el equivalente a

/usr/bin/env node foo

El envcomando ejecuta otro comando cuyo nombre se da en su línea de comando, pasando los siguientes argumentos a ese comando. La razón por la que se usa aquí es que envbuscará $PATHel comando. Entonces, si nodeestá instalado /usr/local/bin/nodey tiene /usr/local/binen su $PATH, el envcomando se invocará /usr/local/bin/node foo.

El propósito principal del envcomando es ejecutar otro comando con un entorno modificado, agregando o eliminando variables de entorno especificadas antes de ejecutar el comando. Pero sin argumentos adicionales, simplemente ejecuta el comando con un entorno sin cambios, que es todo lo que necesita en este caso.

Hay algunos inconvenientes en este enfoque. La mayoría de los sistemas modernos tipo Unix lo han hecho /usr/bin/env, pero trabajé en sistemas más antiguos donde el envcomando se instaló en un directorio diferente. Puede haber limitaciones en los argumentos adicionales que puede pasar usando este mecanismo. Si el usuario no tiene el directorio que contiene el nodecomando $PATH, o tiene un comando diferente llamado node, entonces podría invocar el comando incorrecto o no funcionar en absoluto.

Otros enfoques son:

  • Usar una #! línea que especifique la ruta completa al nodecomando en sí, actualizando el script según sea necesario para diferentes sistemas; o
  • Invoque el nodecomando con su secuencia de comandos como argumento.

Consulte también esta pregunta (y mi respuesta ) para obtener más información sobre la#!/usr/bin/env truco.

Por cierto, en mi sistema (Linux Mint 17.2), está instalado como /usr/bin/nodejs. Según mis notas, cambió de /usr/bin/nodea /usr/bin/nodejsentre Ubuntu 12.04 y 12.10. los#!/usr/bin/env truco no ayudará con eso (a menos que configure un enlace simbólico o algo similar).

ACTUALIZACIÓN: Un comentario de mtraceur dice (reformateado):

Una solución para el problema de nodejs vs node es iniciar el archivo con las siguientes seis líneas:

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

Esto primero intentará nodejsy luego intentará node, y solo imprimirá los mensajes de error si no se encuentran ambos. Una explicación está fuera del alcance de estos comentarios, solo la dejo aquí en caso de que ayude a alguien a lidiar con el problema, ya que esta respuesta planteó el problema.

No he usado NodeJS últimamente. Mi esperanza es que el problema de nodejsvs. nodese haya resuelto en los años desde que publiqué esta respuesta por primera vez. En Ubuntu 18.04, el nodejspaquete se instala /usr/bin/nodejscomo un enlace simbólico a /usr/bin/node. En algunos sistemas operativos anteriores (Ubuntu o Linux Mint, no estoy seguro de cuál), había un nodejs-legacypaquete que se proporcionaba nodecomo enlace simbólico a nodejs. No hay garantía de que tenga todos los detalles correctos.

Keith Thompson
fuente
Una respuesta muy completa que proporciona el por qué de las cosas.
Suraj Jain
1
Una solución para el problema de nodejsvs nodees iniciar el archivo con las siguientes seis líneas: 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) */. Esto primero intentará nodejsy luego intentará node, y solo imprimirá los mensajes de error si no se encuentran ambos. Una explicación está fuera del alcance de estos comentarios, solo la dejo aquí en caso de que ayude a alguien a lidiar con el problema, ya que esta respuesta planteó el problema.
mtraceur
@mtraceur: He incorporado su comentario en mi respuesta. ¿Por qué -en la #!línea?
Keith Thompson
El -in #!/bin/sh -es solo un hábito que asegura que el shell se comporte correctamente en el conjunto de circunstancias extremadamente estrechas e improbables en las que el nombre del script o la ruta relativa que el shell ve comienza con -. (Además, sí, parece que todas las distribuciones principales habían convergido de nuevo nodecomo el nombre principal. No fui a investigar para verificar cuando hice mi comentario, pero por lo que sé, solo el árbol genealógico de la distribución Debian utilizado nodejs, y parece al igual que todos vuelven a los que también apoya nodeuna vez Debian hizo).
mtraceur
Técnicamente, el guión único como primer argumento no significaba "fin de opciones"; originalmente significaba "apagar -xy -v", pero dado que los primeros me gusta de Bourne solo analizaron el primer argumento como opciones posibles, y dado que el shell comienza con esas opciones desactivadas , era abusivo hacer que el shell no intentara analizar el nombre del script desde el original y, por lo tanto, sigue siendo abusable porque el comportamiento se mantiene en los modernos Bourne-like por razones de compatibilidad. Si recuerdo bien toda mi historia de Bourne y trivia de portabilidad.
mtraceur
0

Respuesta corta: es el camino hacia el intérprete.

EDITAR (Respuesta larga): La razón por la que no hay una barra diagonal antes de "nodo" es porque no siempre se puede garantizar la confiabilidad de #! / Bin /. El bit "/ env" hace que el programa sea más multiplataforma al ejecutar el script en un entorno modificado y poder encontrar el programa intérprete de manera más confiable.

No necesariamente lo necesita, pero es bueno usarlo para garantizar la portabilidad (y la profesionalidad)

Cuántico
fuente
1
La /usr/bin/envbroca no modifica el entorno. Es solo un comando en una ubicación (en su mayoría) conocida que invoca otro comando dado como argumento y busca $PATHpara encontrarlo. El punto es que la #!línea requiere la ruta completa al comando que se invoca, y no necesariamente sabe dónde nodeestá instalado.
Keith Thompson
Eso era a lo que me refería, ¡gracias por aclarar!
Quantum