Universal Node.js shebang?

42

Node.js es muy popular en estos días y he estado escribiendo algunos scripts en él. Lamentablemente, la compatibilidad es un problema. Oficialmente, se supone que se llama al intérprete Node.js node, pero Debian y Ubuntu envían un ejecutable llamado en su nodejslugar.

Quiero scripts portátiles con los que Node.js pueda trabajar en tantas situaciones como sea posible. Suponiendo que el nombre de archivo es foo.js, realmente quiero que el script se ejecute de dos maneras:

  1. ./foo.jsejecuta el script si está nodeo nodejsestá en $PATH.
  2. node foo.jstambién ejecuta el script (suponiendo que se llama al intérprete node)

Nota: Las respuestas de xavierm02 y de mí mismo son dos variaciones de un script políglota. Todavía estoy interesado en una solución pura shebang, si existe.

dancek
fuente
Creo que no hay una solución real para esto, ya que los sistemas de compilación pueden nombrar arbitrariamente el ejecutable. No hay nada que le impida nombrar al intérprete de python alphacentauri, solo debe seguir las convenciones y nombrarlo python. Sugeriría usar el nombre estándar nodepara su secuencia de comandos o tener una especie de secuencia de comandos de creación que modifique el shebang.
@ G.Kayaalp Dejando a un lado la política y las convenciones, hay muchos usuarios de Debian / Ubuntu / Fedora, y quiero crear scripts que funcionen para ellos. No quiero configurar un sistema de compilación para esto (¿quién construye los scripts de shell antes de ejecutarlos?), Ni quiero admitirlos alphacentauri. Si hay un ejecutable llamado nodejs, puede estar 99% seguro de que es Node.js. ¿Por qué no apoyar a ambos nodejsy node?
dancek
Instale el paquete nodejs-legacy. La razón por la que esto es necesario es que el nombre es demasiado arrogante genérico, alguien más obtuvo el nombre primero. Sin embargo, ese otro paquete estaba dispuesto a compartir el nombre.
user3710044

Respuestas:

54

Lo mejor que se me ocurrió es este "shebang de dos líneas" que realmente es un script políglota (Bourne shell / Node.js):

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

console.log('Hello world!');

La primera línea es, obviamente, un shebang de shell Bourne. Node.js omite cualquier shebang que encuentre, por lo que este es un archivo javascript válido en lo que respecta a Node.js.

La segunda línea llama al shell no-op :con el argumento //y luego se ejecuta nodejso nodecon el nombre de este archivo como parámetro. command -vse usa en lugar de whichpara portabilidad. La sintaxis de sustitución de comandos $(...)no es estrictamente Bourne, así que opte por backticks si ejecuta esto en la década de 1980.

Node.js solo evalúa la cadena ':', que es como un no-op, y el resto de la línea se analiza como un comentario.

El resto del archivo es simplemente javascript antiguo. El subshell se cierra después de completar la execsegunda línea, por lo que el resto nunca lee el shell.

¡Gracias a xavierm02 por inspiración y a todos los comentaristas por información adicional!

dancek
fuente
1
Gran solución Una alternativa al ':'enfoque es usar // 2>/dev/null(que es lo que nvmhace): bash, es un error ( bash: //: Is a directory), que la redirección 2>/dev/nullignora silenciosamente; a JavaScript, toda la línea se convierte en un comentario. Además, y no espero que esto sea un problema, IRL commandtiene una peculiaridad en la que también informa sobre las funciones de shell y los alias -v, aunque en realidad no los invocaría. Por lo tanto, si ha exportado funciones de shell o incluso alias (suponiendo shopt -s expand_aliases) llamado nodeo nodejs, las cosas podrían romperse.
mklement0
1
Bueno, POSIX sh es omnipresente en estos días (excepto Solaris / bin / sh, pero Solaris viene con AT&T ksh listo para usar, que es compatible con POSIX), y Markus Kuhn recomienda (con justificación) mantenerse alejado de él. En mi humilde opinión, nunca lo necesitas. Pero sí, es suficiente para mksh, bash, dashy otros proyectiles en modernos sistemas Unix y GNU.
mirabilos
1
Gran solución; aunque aún más portátil sería usar en #!/usr/bin/env shlugar de #!/bin/sh(por en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089
2
Tendría cuidado con la publicidad #!/usr/bin/env shcomo "más portátil". Ese mismo artículo de Wikipedia dice "Esto funciona principalmente porque la ruta / usr / bin / env se usa comúnmente para la utilidad env ..." Eso no es un respaldo maravilloso, y supongo que te encontrarás con sistemas sin /usr/bin/envmás frecuencia que te encuentras con sistemas sin /bin/sh, si hay alguna diferencia en frecuencia.
sheldonh
1
@dancek Hola desde 2018, ¿no sería //bin/sh -c :; exec ...una versión más limpia de la segunda línea?
har-wradim
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falsees lo mismo que, /bin/falseexcepto que la segunda barra oblicua lo transforma en un comentario para el nodo, y es por eso que está aquí. Luego, ||se evalúa el lado derecho del primero . 'which node || which nodejs'con comillas inversas en lugar de comillas, inicia el nodo y lo <<alimenta lo que esté a la derecha. Podría haber usado un delimitador comenzando //como dancek, hubiera funcionado, pero me parece más limpio tener solo dos líneas al principio, así que solía tail -n +2 $0leer el archivo solo, excepto las dos primeras líneas.

Y si lo ejecuta en el nodo, la primera línea se reconoce como un shebang y se ignora y la segunda es un comentario de una línea.

(Aparentemente, sed podría usarse para reemplazar el contenido del archivo Print de cola sin la primera y última línea )


Responda antes de editar:

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

No puedes hacer lo que quieres, así que lo que haces es ejecutar un script de shell, de ahí el #!/bin/sh. Ese script de shell obtendrá la ruta del archivo necesario para ejecutar el nodo, es decir which node || which nodejs. Las comillas inversas están aquí para que se ejecuten, por lo que 'which node || which nodejs'(con las comillas inversas en lugar de las comillas) simplemente llama al nodo. Luego, simplemente alimente su script con él <<. El __HERE__son los delimitadores de secuencia de comandos de la suya. Y el console.log('ok');es un ejemplo de secuencia de comandos que debe reemplazar con su secuencia de comandos.

xavierm02
fuente
una explicación sería buena
0xC0000022L
Mejor citar el documento interno delimitador para evitar la expansión de parámetro, sustitución de orden y expansión aritmética que se realizará en el código JavaScript antes de la ejecución: <<'__HERE__'.
manatwork
¡Esto se está poniendo interesante! Aunque //bin/falseno funciona en mi entorno MSYS, y necesito citas alrededor de los backticks ya que mi nodereside en C:\Program Files\.... Sí, trabajo en un ambiente horrible ...
dancek
1
//bin/falsetampoco funciona en Mac OS X. Lo siento, pero esto ya no parece muy portátil.
dancek
2
El whichcomando tampoco es portátil.
jordanm
9

Esto es solo un problema en los sistemas basados ​​en Debian, donde la política superó el sentido.

No sé cuándo Fedora proporcionó un binario llamado nodejs, pero nunca lo vi. El paquete se llama nodejs e instala un binario llamado nodo.

Simplemente use un enlace simbólico para aplicar el sentido común a sus sistemas basados ​​en Debian, y luego puede usar un shebang sano. Otras personas usarán shebangs sanos de todos modos, por lo que necesitarás ese enlace simbólico.

#!/usr/bin/env node

console.log("Spread the love.");
Sheldonh
fuente
Lo siento, pero esto no responde la pregunta. Entiendo el punto de vista político, pero ese no es el punto aquí.
dancek
Para el registro, algunos sistemas Fedora tienen un nodejsejecutable , pero eso no es culpa de Fedora. Perdón por tergiversar el hecho.
dancek
8
No hay necesidad de disculparse. Tienes toda la razón. Mi respuesta no responde a tu pregunta. Responde a su pregunta y sugiere que la pregunta limita el alcance a soluciones menos útiles que las disponibles. Estoy ofreciendo consejos prácticos informados por la historia. Node no es el primer intérprete que se encuentra aquí. Deberías haber visto la conmoción que llevó a Larry Wall a declarar que el perl shebang DEBE ser #!/usr/bin/perl. Dicho esto, no está obligado a dar me gusta o aplicar mis sugerencias. Paz.
Sheldonh
3

Si no le importa crear un pequeño .sharchivo, tengo una pequeña solución para usted. Puede crear un pequeño script de shell para determinar qué nodo ejecutable usar, y usar este script en su shebang:

shebang.sh :

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

script.js :

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

Marque ambos ejecutables y ejecute ./script.js.

De esta manera, evita las secuencias de comandos políglotas. No creo que sea posible usar varias líneas shebang, aunque parece una buena idea.

Aunque esto resuelve el problema de la manera que desea, parece que a nadie le importa esto. Por ejemplo, uglifyjs y coffeescript usa #!/usr/bin/env node, npm usa un script de shell como punto de entrada, que nuevamente llama al ejecutable explícitamente con nombre node. Soy un usuario de Ubuntu, y no sabía esto, ya que siempre compilo el nodo. Estoy considerando reportar esto como un error.


fuente
Estoy bastante seguro de que al menos tiene que preguntar al usuario chmod +xen el guión sh ... por lo que también podría pedir que establecer una variable que da la ubicación de su archivo ejecutable nodo ...
xavierm02
@ xavierm02 Uno puede lanzar el script chmod +x'd. Y estoy de acuerdo en que una variable NODE es mejor que which node || which nodejs. Pero el investigador quiere proporcionar una experiencia lista para usar, aunque muchos proyectos importantes de nodos simplemente lo utilizan #!/usr/bin/env node.
1

Solo para completar, aquí hay un par de otras formas de hacer la segunda línea:

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

Pero tampoco tiene ninguna ventaja sobre la respuesta seleccionada:

':' //; || echo yes

Además, si sabía que se encontraría uno nodeo nodejs(pero no ambos), lo siguiente funciona:

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

Pero ese es un gran "si", así que creo que la respuesta seleccionada sigue siendo la mejor.

Dave Mason
fuente
Además, puede ejecutar cualquiera foobar // 2>/dev/nullsiempre que foobarno sea un comando. Y muchas de las utilidades que se encuentran en cualquier sistema POSIX se pueden ejecutar con el //argumento.
dancek
1

Entiendo que esto no responde la pregunta, pero estoy convencido de que la pregunta se hace con una premisa falsa.

Ubuntu está equivocado aquí. Escribir un shebang universal para su propio script no cambiará otros paquetes sobre los que no tiene control y que usan el estándar de facto #!/usr/bin/env node. Su sistema debe proporcionar nodeen PATHsi lo desea para cualquier script de orientación nodejs a correr en él.

Por ejemplo, ni siquiera el npmpaquete provisto por Ubuntu reescribe shebangs en paquetes:

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