我在#!/usr/bin/env node
的一些示例的开头看到了这一行nodejs
,并且我搜索了一下没有找到任何可以回答该行原因的主题。
单词的性质使搜索变得不那么容易。
我最近阅读了一些javascript
和nodejs
本书,我不记得在其中任何一本书中看过它。
如果你想要一个例子,你可以看到RabbitMQ
官方tutorial,他们几乎在所有的例子中都有它,这里有一个:
#!/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);
});
有人可以解释一下这条线的含义是什么吗?
如果我放入或删除此行有什么区别?在什么情况下我需要它?
答案 0 :(得分:105)
#!/usr/bin/env node
是shebang line 的一个实例:在类Unix平台上的可执行纯文本文件中的第一行通过魔术#!
前缀后面的命令行(称为 shebang )告诉系统将该文件传递给执行的解释器。
注意: Windows 不支持shebang行,因此他们有效忽略;在Windows上,它只是一个给定文件的文件扩展名,它决定了可执行文件将对其进行解释。 但是,您仍然需要npm
。 [1]
以下
在下面的讨论中,我假设包含由Node.js执行的源代码的文件名为file
。
你需要这一行,如果你想直接调用Node.js源文件 ,作为一个可执行文件本身 - 这假定使用chmod +x ./file
之类的命令将文件标记为可执行文件,然后允许您使用./file
调用该文件,或者,如果它位于其中一个目录中列在$PATH
变量中,简称为file
。
npm
;另请参阅"bin"
key in a package's package.json
file了解如何使用全局安装的软件包。脚注 [1] 显示了如何在Windows上处理它。您不需要这一行通过node
解释器明确调用文件,例如node ./file
可选背景信息:
#!/usr/bin/env <executableName>
是一种可移植指定解释器的方式:简而言之,它表示:执行<executableName>
,无论您(第一次)在列出的目录中找到它$PATH
变量(并隐式传递给手头文件的路径)。
这说明了一个给定的解释器可能安装在跨平台的不同位置这一事实,这绝对是node
Node.js二进制文件的情况。
相比之下,env
实用程序本身的位置可以依赖于跨平台的相同位置,即/usr/bin/env
- 并指定在shebang行中,可执行文件的完整路径 required 。
请注意POSIX实用程序env
正在重新调整用途,以便按文件名定位并在$PATH
中执行可执行文件。
env
的真正目的是管理命令的环境 - 请参阅this answer和env
's POSIX spec。
同样值得注意的是Node.js正在为shebang行制作语法 exception ,因为它们不是有效的JavaScript代码(#
不是JavaScript中的注释字符,与类似POSIX的shell和其他解释器不同。
[1]为了跨平台一致性, npm
在Windows上创建包装 *.cmd
文件(批处理文件)安装包package.json
文件中指定的可执行文件时(通过"bin"
属性)。基本上,这些包装批处理文件模仿 Unix shebang功能:它们使用shebang行中指定的可执行文件显式调用目标文件 - 因此,您的脚本必须包含shebang line,即使你只打算在Windows上运行它们 - 请参阅我的Keith Thompson's helpful answer了解详细信息。
由于可以在没有*.cmd
扩展名的情况下调用.cmd
文件,因此可以实现无缝的跨平台体验:在Windows和Unix上,您可以有效地调用npm
- 安装的CLI ,无扩展名称。
功能
答案 1 :(得分:24)
由解释程序执行的脚本通常在顶部有一个shebang line来告诉操作系统如何执行它们。
如果您有一个名为foo
的脚本,其第一行是#!/bin/sh
,系统将读取第一行并执行等效的/bin/sh foo
。因此,大多数解释器都设置为接受脚本文件的名称作为命令行参数。
#!
后面的解释器名称必须是完整路径;操作系统不会搜索您的$PATH
以找到解释器。
如果你有一个由node
执行的脚本,写第一行的显而易见的方法是:
#!/usr/bin/node
但如果在node
中未安装/usr/bin
命令,则该功能无效。
常见的解决方法是使用env
命令(实际上
#!/usr/bin/env node
如果您的脚本名为foo
,则操作系统将执行与
/usr/bin/env node foo
env
命令执行另一个命令,该命令的名称在其命令行中给出,并将任何后续参数传递给该命令。这里使用的原因是env
将搜索$PATH
命令。因此,如果node
中安装了/usr/local/bin/node
,并且/usr/local/bin
中有$PATH
,则env
命令将调用/usr/local/bin/node foo
。
env
命令的主要目的是使用修改后的环境执行另一个命令,在运行命令之前添加或删除指定的环境变量。但是没有额外的参数,它只是在环境不变的情况下执行命令,在这种情况下你只需要它。
这种方法有一些缺点。大多数现代类Unix系统都有/usr/bin/env
,但我在较旧的系统上工作,其中env
命令安装在不同的目录中。您可以使用此机制传递的其他参数可能存在限制。如果用户没有在node
中包含$PATH
命令的目录,或者有一个名为node
的不同命令,那么它可以调用错误的命令或根本不工作。
其他方法是:
#!
行指定node
命令本身的完整路径,根据需要更新不同系统的脚本;或node
命令。有关#!/usr/bin/env
技巧的更多讨论,另请参阅this question(和my answer)。
顺便说一句,在我的系统(Linux Mint 17.2)上,它安装为/usr/bin/nodejs
。根据我的说明,它在Ubuntu 12.04和12.10之间从/usr/bin/node
变为/usr/bin/nodejs
。 #!/usr/bin/env
技巧不会对此有所帮助(除非您设置符号链接或类似的东西)。
答案 2 :(得分:2)
Linux 内核的 exec
系统调用本机理解 shebangs (#!
)
当你使用 bash 时:
./something
在 Linux 上,这将调用 exec
系统调用,路径为 ./something
。
内核的这一行在传递给 exec
的文件上被调用:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
它读取文件的第一个字节,并将它们与 #!
进行比较。
如果比较结果为真,则该行的其余部分由 Linux 内核解析,它会再次调用 exec
:
/usr/bin/env
node
因此相当于:
/usr/bin/env node /path/to/script.js
env
是一个可执行文件,它搜索 PATH
以例如找到/usr/bin/node
,然后最后调用:
/usr/bin/node /path/to/script.js
Node.js 解释器确实看到文件中的 #!
行,但它必须被编程为忽略该行,即使 #
在 Node 中通常不是有效的注释字符(不像许多其他语言,如 Python),另见:Pound Sign (#) As Comment Start In JavaScript?
是的,您可以使用以下方法进行无限循环:
printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a
Bash 识别出错误:
-bash: /a: /a: bad interpreter: Too many levels of symbolic links
#!
恰好是人类可读的,但这不是必需的。
如果文件以不同的字节开始,那么 exec
系统调用将使用不同的处理程序。另一个最重要的内置处理程序是针对 ELF 可执行文件的:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305,它检查字节 7f 45 4c 46
(对于 .ELF
,它也恰好是人类可读的)。让我们通过读取 /bin/ls
的前 4 个字节来确认,这是一个 ELF 可执行文件:
head -c 4 "$(which ls)" | hd
输出:
00000000 7f 45 4c 46 |.ELF|
00000004
因此,当内核看到这些字节时,它会获取 ELF 文件,将其正确放入内存中,并使用它启动一个新进程。另见:How does kernel get an executable binary file running under linux?
最后,您可以使用 binfmt_misc
机制添加您自己的 shebang 处理程序。例如,您可以添加 custom handler for .jar
files。这种机制甚至支持文件扩展名的处理程序。另一个应用是transparently run executables of a different architecture with QEMU。
我不认为 POSIX 指定了 shebangs 但是: https://unix.stackexchange.com/a/346214/32558 ,尽管它确实在基本原理部分中提到,并且以“如果系统支持可执行脚本,可能会发生某些事情”的形式出现。不过 macOS 和 FreeBSD 似乎也实现了它。
PATH
搜索动机
很可能,shebangs 存在的一大动机是在 Linux 中,我们经常希望从 PATH
运行命令,就像:
basename-of-command
代替:
/full/path/to/basename-of-command
但是,如果没有 shebang 机制,Linux 怎么知道如何启动每种类型的文件?
在命令中硬编码扩展:
basename-of-command.js
或在每个解释器上实现 PATH 搜索:
node basename-of-command
有可能,但这有一个主要问题,如果我们决定将命令重构为另一种语言,一切都会中断。
Shebangs 很好地解决了这个问题。
答案 3 :(得分:0)
简短回答: 这是解释者的路径。
编辑(长答案): 在“节点”之前没有斜杠的原因是因为你不能总是保证#!/ bin /的可靠性。 “/ env”位通过在修改后的环境中运行脚本并且更可靠地找到解释程序,使程序更具跨平台性。您不一定需要它,但最好使用它来确保可移植性(和专业性)