通用Node.js shebang?


42

如今,Node.js非常受欢迎,我一直在上面编写一些脚本。不幸的是,兼容性是一个问题。正式地,应该将Node.js解释器称为node,但是Debian和Ubuntu附带了一个名为的可执行文件nodejs

我希望Node.js可以在尽可能多的情况下使用的可移植脚本。假设文件名是foo.js,我真的希望脚本以两种方式运行:

  1. ./foo.js如果nodenodejs位于中,则运行脚本$PATH
  2. node foo.js也运行脚本(假设解释器称为node

注意: xavierm02和我本人的答案是多语言脚本的两个变体。我仍然对纯粹的shebang解决方案感兴趣,如果有的话。


我认为对此没有真正的解决方案,因为构建系统可以随意命名可执行文件。没有什么可以阻止您命名python解释器alphacentauri,您只需遵循约定并将其命名为python。我建议node您为脚本使用标准名称,或者使用一种可以修改shebang的make脚本。

除了@ G.Kayaalp政治和惯例,还有很多Debian / Ubuntu / Fedora用户,我想制作适合他们的脚本。我不想为此建立一个构建系统(谁在运行它们之前先构建shell脚本?),我也不想为此提供支持alphacentauri。如果有一个名为的可执行文件nodejs,则可以确保它有99%是Node.js。为什么不同时支持nodejsnode
dancek

安装nodejs-legacy软件包。之所以需要此名称,是因为该名称太过夸张了,其他人首先获得了该名称。但是,其他软件包愿意共享这个名称。
user3710044

Answers:


54

我想出的最好的就是这个“两行shebang”,它实际上是一个多语言(Bourne shell / Node.js)脚本:

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

console.log('Hello world!');

第一行显然是一个Bourne shell shebang。Node.js绕过它找到的所有shebang,因此就Node.js而言,这是一个有效的javascript文件。

第二行:使用参数调用shell no-op ,//然后执行nodejsnode以文件名作为参数。command -v用于代替which可移植性。命令替换语法$(...)严格来说并不是Bourne,因此如果在1980年代运行它,请选择反引号。

Node.js只是评估string ':',就像没有操作,其余的行都被解析为注释。

该文件的其余部分只是普通的旧javascript。子shell exec在第二行完成后退出,因此shell永远不会读取文件的其余部分。

感谢xavierm02的启发,以及所有评论者的其他信息!


1
很好的解决方案。该':'方法的替代方法是使用// 2>/dev/nullnvm确实如此):bash是错误(bash: //: Is a directory),重定向会自动2>/dev/null忽略该错误()。在JavaScript中,整行变成注释。另外-我并不认为这是一个问题,IRL- command有一个古怪之处,它也报告了shell函数和别名-v-尽管它实际上并没有调用它们。因此,如果您碰巧导出了shell函数或什至别名(假设为shopt -s expand_aliases)命名为nodenodejs,则事情可能会中断。
mklement0 2013年

1
好吧,如今POSIX sh无处不在(Solaris / bin / sh除外-但是Solaris确实开箱即用,是AT&T ksh,与POSIX兼容),Markus Kuhn建议(有理由地)不要这样做。恕我直言,您永远不需要它。但是,是的,这足以让mkshbashdash在现代Unix和GNU系统和其他炮弹。
mirabilos 2013年

1
很好的解决方案;但更便携是使用#!/usr/bin/env sh替代#!/bin/sh(每en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability
user7089

2
我会谨慎地将广告宣传#!/usr/bin/env sh为“更具便携性”。相同的Wikipedia文章说:“这主要是因为/ usr / bin / env路径通常用于env实用程序...”,这并不是一个很好的认可,我的猜测是,您进入系统的/usr/bin/env频率不会超过/bin/sh如果根本没有频率差异,您就会遇到没有的系统。
sheldonh 2015年

1
@dancek您好,2018年起,会不会是//bin/sh -c :; exec ...第二行的较干净版本?
har-wradim

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

//bin/false/bin/false除了第二个斜杠将其转换为对节点的注释之外,其他都是相同的,这就是它在这里的原因。然后,第一个的右侧||被评估。'which node || which nodejs'用反引号(而不是引号)启动节点,并<<在右侧提供任何内容。我本来可以//像deskk那样使用定界符开始的,它会起作用,但是我发现它更干净,开始时只有两行,所以我tail -n +2 $0以前让文件读取了自己,除了前两行。

并且,如果在节点中运行它,则第一行将被识别为shebang并被忽略,第二行是单行注释。

(显然,sed可用于替换没有第一行和最后一行的尾部打印文件内容


编辑前回答:

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

您无法做您想做的事情,因此您要做的是运行一个shell脚本,因此是#!/bin/sh。该shell脚本将获取执行节点所需的文件路径,即which node || which nodejs。反引号在这里以便执行,因此'which node || which nodejs'(用反引号代替引号)只需调用node。然后,您只需使用即可向脚本中输入脚本<<。该__HERE__是你的脚本的分隔符。并且console.log('ok');是一个脚本示例,您应该将其替换为脚本。


一个解释会很好
0xC0000022L13

最好引用此处的定界符,以避免在执行之前对JavaScript代码执行参数扩展,命令替换和算术扩展<<'__HERE__'
manatwork

这变得越来越有趣!尽管//bin/false在我的MSYS环境中不起作用,但是当我node位于时,我需要在反引号周围加上引号C:\Program Files\...。是的,我在一个可怕的环境中工作……
dancek

1
//bin/false在Mac OS X上也不起作用。抱歉,这似乎不再具有可移植性。
dancek

2
which命令也不是可移植的。
jordanm

9

这仅是基于Debian的系统上的一个问题,在该系统上策略已无法解决。

我不知道Fedora何时提供了一个名为nodejs的二进制文件,但我从未见过。该软件包称为nodejs,它安装了一个名为node的二进制文件。

只需使用符号链接将常识应用于基于Debian的系统,然后即可使用理智的shebang。无论如何,其他人将使用理智的shebang,所以您将需要该符号链接。

#!/usr/bin/env node

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

抱歉,这不能解决问题。我确实了解政治观点,但这不是这里的重点。
dancek

作为记录,某些Fedora系统确实具有nodejs可执行文件,但这不是Fedora的错。对不起,事实陈述不正确。
dancek

8
不用道歉 你说得很对。我的回答没有回答您的问题。它说出了您的问题,表明该问题将范围限制为不可用的有用解决方案。我会根据历史为您提供实用的建议。Node不是在这里找到自己的第一个解释器。您应该已经看到导致拉里·沃尔(Larry Wall)宣布骚扰Perl shebang的骚动#!/usr/bin/perl。也就是说,您没有义务喜欢或应用我的建议。和平。
sheldonh

3

如果您不介意创建一个小.sh文件,那么我为您提供了一些解决方案。您可以创建一个小shell脚本来确定要使用的节点可执行文件,然后在您的shebang中使用此脚本:

shebang.sh

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

script.js

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

将它们都标记为可执行文件,然后运行./script.js

这样,您可以避免使用多语言脚本。尽管这似乎是一个好主意,但我认为无法使用多个shebang行。

尽管这以您想要的方式解决了问题,但似乎没人在乎。例如,uglifyjscoffeescript使用#!/usr/bin/env nodenpm使用外壳程序脚本作为入口点,该脚本再次使用name显式调用可执行文件node。我是Ubuntu用户,我不知道这一点,因为我总是编译节点。我正在考虑将其报告为错误。


我非常确定您至少必须要求用户chmod +x在sh脚本上...所以您最好也请他们设置一个变量,以将位置提供给其节点可执行文件...
xavierm02

@ xavierm02一个人可以释放脚本chmod +x'd。我同意NODE变量比更好which node || which nodejs。但是,尽管许多主要的节点项目都只是使用,但查询者还是希望提供开箱即用的体验#!/usr/bin/env node

1

为了完整起见,这里有几种其他的第二行方法:

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

但是,相对于所选答案,它们都没有任何优势:

':' //; || echo yes

另外,如果您知道将找到一个nodenodejs(但不是两个),则可以进行以下操作:

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

但这是一个很大的“如果”,因此我认为所选答案仍然是最佳答案。


此外,foobar // 2>/dev/null只要foobar不是命令,就可以运行任何命令。并且在任何POSIX系统上找到的许多实用程序都可以使用//参数运行。
dancek '16

1

我知道这不能回答问题,但是我确信问题是在错误的前提下提出的。

Ubuntu在这里是错的。为自己的脚本编写通用的shebang不会更改其他您无法控制使用实际标准的软件包#!/usr/bin/env node。您的系统是否必须提供nodein PATH,才能让任何针对nodejs的脚本在其上运行。

例如,甚至Ubuntu提供的npm软件包都不会重写软件包中的shebang:

$ 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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.