节点文件开头的“/usr/bin/env节点”到底是做什么的?
What exactly does "/usr/bin/env node" do at the beginning of node files?
我在 nodejs
中的一些示例的开头看到了这一行 #!/usr/bin/env node
,我用 google 搜索但没有找到任何可以回答该行原因的主题。
单词的性质使搜索变得不那么容易。
我最近读了一些 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);
});
谁能解释一下这行是什么意思?
放置或移除这条线有什么区别?在什么情况下我需要它?
简答:
这是解释器的路径。
编辑(长答案):
"node" 之前没有斜杠的原因是你不能总是保证 #!/bin/ 的可靠性。 “/env”位通过 运行 修改环境中的脚本使程序更跨平台,并且能够更可靠地找到解释器程序。
你不一定需要它,但是为了保证便携性(和专业性)用起来还是不错的
#!/usr/bin/env node
是 shebang line 的实例:[=] 上可执行纯文本文件的第一行89=]类 Unix 平台 告诉系统将那个文件传递给哪个解释器来执行,通过紧跟魔术 #!
前缀的命令行(称为 shebang).
注意:Windows不支持shebang行,所以他们那里实际上 被忽略了 ;在 Windows 上,只有给定文件的 文件扩展名 决定了哪个可执行文件将解释它。 但是,在 npm
.[1]
的上下文中,您仍然需要它们
以下 shebang 行的一般讨论 仅限于类 Unix 平台:
在下面的讨论中,我假设包含由 Node.js 执行的源代码的文件被简单命名为 file
.
你需要这一行,如果你想直接调用Node.js源文件,作为其自身的可执行文件 - 这假定文件已使用 chmod +x ./file
等命令标记为可执行文件,然后允许您使用 ./file
等命令调用文件,或者,如果它位于 $PATH
变量中列出的目录之一,则简单地表示为 file
.
- 具体来说,您需要一个 shebang 行来创建基于 Node.js 源文件的 CLI 作为 npm package 的一部分, CLI(s) 由
npm
根据 "bin"
key in a package's package.json
file; also see this answer 的值安装,以了解它如何与 globally 安装的软件包一起工作。脚注 [1] 显示了如何在 Windows. 上处理
您不需要这一行来通过node
解释器显式调用文件,例如node ./file
可选背景信息:
#!/usr/bin/env <executableName>
是 可移植地 指定解释器的一种方式:简而言之,它说:在你(首先)找到它的任何地方执行 <executableName>
$PATH
变量中列出的目录(并隐式地将其传递给手头文件的路径)。
这解释了给定解释器可能跨平台安装在不同位置的事实,node
、Node.js 二进制文件就是这种情况。
相比之下,env
实用程序本身的位置可以依赖于在跨平台的 相同 位置,即 /usr/bin/env
- 和在 shebang 行中 需要 指定可执行文件的 完整 路径。
请注意 POSIX 实用程序 env
正在 重新调整用途 以按文件名定位并在 $PATH
中执行可执行文件。
env
的真正目的是管理命令的环境 - 请参阅 env
's POSIX spec and 。
还值得注意的是,Node.js 正在为 shebang 行创建语法 exception,因为它们不是有效的 JavaScript 代码(#
不是 JavaScript 中的注释字符,不像 POSIX-like shell 和其他解释器)。
[1] 为了跨平台的一致性,npm
创建了 wrapper *.cmd
文件(批处理文件)在 Windows 安装包的 package.json
文件中指定的可执行文件时(通过 "bin"
属性)。本质上,这些包装器批处理文件模仿 Unix shebang 功能:它们使用shebang 行 中指定的可执行文件显式调用目标文件 - 因此,你的脚本必须包含一个 shebang 行,即使你只打算在 Windows 上 运行 它们 - 请参阅我的 this answer 了解详细信息。
由于可以在没有 .cmd
扩展名的情况下调用 *.cmd
文件,因此可以实现无缝的跨平台体验:在 Windows 和 Unix 上,您都可以有效地调用 npm
-installed CLI 以其原始的、无扩展的名称命名。
由解释器执行的脚本通常在顶部有一个 shebang line 来告诉 OS 如何执行它们。
如果您有一个名为 foo
的脚本,其第一行是 #!/bin/sh
,系统将读取第一行并执行 /bin/sh foo
的等价物。因此,大多数解释器都设置为接受脚本文件的名称作为命令行参数。
#!
后面的解释器名称必须是完整路径; OS 不会搜索您的 $PATH
来查找解释器。
如果你有一个脚本要被node
执行,那么第一行的明显写法是:
#!/usr/bin/node
但如果 /usr/bin
中未安装 node
命令,那将不起作用。
一个常见的解决方法是使用 env
命令(实际上 并不是为此目的而设计的):
#!/usr/bin/env node
如果您的脚本名为 foo
,OS 将执行与
相同的操作
/usr/bin/env node foo
env
命令执行另一个命令,该命令的名称在其命令行中给出,并将以下任何参数传递给该命令。此处使用它的原因是 env
将在 $PATH
中搜索命令。因此,如果 node
安装在 /usr/local/bin/node
中,并且您的 $PATH
中有 /usr/local/bin
,则 env
命令将调用 /usr/local/bin/node foo
.
env
命令的主要目的是在修改后的环境下执行另一个命令,在运行命令之前添加或删除指定的环境变量。但是没有额外的参数,它只是在没有改变的环境下执行命令,这就是你在这种情况下所需要的。
这种方法有一些缺点。大多数现代类 Unix 系统都有 /usr/bin/env
,但我在较旧的系统上工作,其中 env
命令安装在不同的目录中。您可以使用此机制传递的其他参数可能存在限制。如果用户 没有 在 $PATH
中有包含 node
命令的目录,或者有一些不同的命令叫做 node
,那么它可以调用命令错误或根本不起作用。
其他方法是:
- 使用
#!
行指定 node
命令本身的完整路径,根据不同系统的需要更新脚本;或
- 使用您的脚本作为参数调用
node
命令。
另请参阅 this question (and my answer) 以了解有关 #!/usr/bin/env
技巧的更多讨论。
顺便说一句,在我的系统 (Linux Mint 17.2) 上,它安装为 /usr/bin/nodejs
。根据我的笔记,它在 Ubuntu 12.04 和 12.10 之间从 /usr/bin/node
变为 /usr/bin/nodejs
。 #!/usr/bin/env
技巧对此无济于事(除非您设置符号链接或类似的东西)。
更新:mtraceur 的评论说(重新格式化):
A workaround for the nodejs vs node problem is to start the file with
the following six lines:
#!/bin/sh -
':' /*-
test1=$(nodejs --version 2>&1) && exec nodejs "[=13=]" "$@"
test2=$(node --version 2>&1) && exec node "[=13=]" "$@"
exec printf '%s\n' "$test1" "$test2" 1>&2
*/
This will first try nodejs
and then try node
, and only
print the error messages if both of them are not found. An explanation
is out of scope of these comments, I'm just leaving it here in case it
helps anyone deal with the problem since this answer brought the
problem up.
我最近没有使用 NodeJS。我希望 nodejs
与 node
问题在我首次发布此答案后的几年内得到解决。在 Ubuntu 18.04 上,nodejs
包安装 /usr/bin/nodejs
作为指向 /usr/bin/node
的符号链接。在一些较早的 OS(Ubuntu 或 Linux Mint,我不确定是哪个)上,有一个 nodejs-legacy
包提供 node
作为符号链接nodejs
。不能保证我的所有细节都正确。
Linux 内核的 exec
系统调用本机理解 shebangs (#!
)
当你在 bash:
./something
在 Linux 上,这会使用路径 ./something
.
调用 exec
系统调用
内核的这一行在传递给 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 解释器确实看到文件中的 #!
行,但它必须被编程为忽略该行,即使 #
通常不是文件中的有效注释字符节点(不同于许多其他语言,例如 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. This mechanism even supports handlers by file extension. Another application is to transparently run executables of a different architecture with QEMU.
我不认为 POSIX specifies shebangs however: https://unix.stackexchange.com/a/346214/32558 ,尽管它确实在基本原理部分中以“如果系统支持可执行脚本,则可能会发生某些事情”的形式提到。然而,macOS 和 FreeBSD 似乎也实现了它。
PATH
搜索动机
shebang 存在的一大动机可能是在 Linux 中,我们经常希望 运行 来自 PATH
的命令,就像:
basename-of-command
而不是:
/full/path/to/basename-of-command
但是,如果没有 shebang 机制,Linux 怎么知道如何启动每种类型的文件?
在命令中对扩展进行硬编码:
basename-of-command.js
或在每个解释器上执行 PATH 搜索:
node basename-of-command
是有可能的,但是如果我们决定将命令重构为另一种语言,这会导致一切都崩溃。
Shebangs 很好地解决了这个问题。
我在 nodejs
中的一些示例的开头看到了这一行 #!/usr/bin/env node
,我用 google 搜索但没有找到任何可以回答该行原因的主题。
单词的性质使搜索变得不那么容易。
我最近读了一些 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);
});
谁能解释一下这行是什么意思?
放置或移除这条线有什么区别?在什么情况下我需要它?
简答: 这是解释器的路径。
编辑(长答案): "node" 之前没有斜杠的原因是你不能总是保证 #!/bin/ 的可靠性。 “/env”位通过 运行 修改环境中的脚本使程序更跨平台,并且能够更可靠地找到解释器程序。
你不一定需要它,但是为了保证便携性(和专业性)用起来还是不错的
#!/usr/bin/env node
是 shebang line 的实例:[=] 上可执行纯文本文件的第一行89=]类 Unix 平台 告诉系统将那个文件传递给哪个解释器来执行,通过紧跟魔术 #!
前缀的命令行(称为 shebang).
注意:Windows不支持shebang行,所以他们那里实际上 被忽略了 ;在 Windows 上,只有给定文件的 文件扩展名 决定了哪个可执行文件将解释它。 但是,在 npm
.[1]
以下 shebang 行的一般讨论 仅限于类 Unix 平台:
在下面的讨论中,我假设包含由 Node.js 执行的源代码的文件被简单命名为 file
.
你需要这一行,如果你想直接调用Node.js源文件,作为其自身的可执行文件 - 这假定文件已使用
chmod +x ./file
等命令标记为可执行文件,然后允许您使用./file
等命令调用文件,或者,如果它位于$PATH
变量中列出的目录之一,则简单地表示为file
.- 具体来说,您需要一个 shebang 行来创建基于 Node.js 源文件的 CLI 作为 npm package 的一部分, CLI(s) 由
npm
根据"bin"
key in a package'spackage.json
file; also see this answer 的值安装,以了解它如何与 globally 安装的软件包一起工作。脚注 [1] 显示了如何在 Windows. 上处理
- 具体来说,您需要一个 shebang 行来创建基于 Node.js 源文件的 CLI 作为 npm package 的一部分, CLI(s) 由
您不需要这一行来通过
node
解释器显式调用文件,例如node ./file
可选背景信息:
#!/usr/bin/env <executableName>
是 可移植地 指定解释器的一种方式:简而言之,它说:在你(首先)找到它的任何地方执行 <executableName>
$PATH
变量中列出的目录(并隐式地将其传递给手头文件的路径)。
这解释了给定解释器可能跨平台安装在不同位置的事实,node
、Node.js 二进制文件就是这种情况。
相比之下,env
实用程序本身的位置可以依赖于在跨平台的 相同 位置,即 /usr/bin/env
- 和在 shebang 行中 需要 指定可执行文件的 完整 路径。
请注意 POSIX 实用程序 env
正在 重新调整用途 以按文件名定位并在 $PATH
中执行可执行文件。
env
的真正目的是管理命令的环境 - 请参阅 env
's POSIX spec and
还值得注意的是,Node.js 正在为 shebang 行创建语法 exception,因为它们不是有效的 JavaScript 代码(#
不是 JavaScript 中的注释字符,不像 POSIX-like shell 和其他解释器)。
[1] 为了跨平台的一致性,npm
创建了 wrapper *.cmd
文件(批处理文件)在 Windows 安装包的 package.json
文件中指定的可执行文件时(通过 "bin"
属性)。本质上,这些包装器批处理文件模仿 Unix shebang 功能:它们使用shebang 行 中指定的可执行文件显式调用目标文件 - 因此,你的脚本必须包含一个 shebang 行,即使你只打算在 Windows 上 运行 它们 - 请参阅我的 this answer 了解详细信息。
由于可以在没有 .cmd
扩展名的情况下调用 *.cmd
文件,因此可以实现无缝的跨平台体验:在 Windows 和 Unix 上,您都可以有效地调用 npm
-installed CLI 以其原始的、无扩展的名称命名。
由解释器执行的脚本通常在顶部有一个 shebang line 来告诉 OS 如何执行它们。
如果您有一个名为 foo
的脚本,其第一行是 #!/bin/sh
,系统将读取第一行并执行 /bin/sh foo
的等价物。因此,大多数解释器都设置为接受脚本文件的名称作为命令行参数。
#!
后面的解释器名称必须是完整路径; OS 不会搜索您的 $PATH
来查找解释器。
如果你有一个脚本要被node
执行,那么第一行的明显写法是:
#!/usr/bin/node
但如果 /usr/bin
中未安装 node
命令,那将不起作用。
一个常见的解决方法是使用 env
命令(实际上 并不是为此目的而设计的):
#!/usr/bin/env node
如果您的脚本名为 foo
,OS 将执行与
/usr/bin/env node foo
env
命令执行另一个命令,该命令的名称在其命令行中给出,并将以下任何参数传递给该命令。此处使用它的原因是 env
将在 $PATH
中搜索命令。因此,如果 node
安装在 /usr/local/bin/node
中,并且您的 $PATH
中有 /usr/local/bin
,则 env
命令将调用 /usr/local/bin/node foo
.
env
命令的主要目的是在修改后的环境下执行另一个命令,在运行命令之前添加或删除指定的环境变量。但是没有额外的参数,它只是在没有改变的环境下执行命令,这就是你在这种情况下所需要的。
这种方法有一些缺点。大多数现代类 Unix 系统都有 /usr/bin/env
,但我在较旧的系统上工作,其中 env
命令安装在不同的目录中。您可以使用此机制传递的其他参数可能存在限制。如果用户 没有 在 $PATH
中有包含 node
命令的目录,或者有一些不同的命令叫做 node
,那么它可以调用命令错误或根本不起作用。
其他方法是:
- 使用
#!
行指定node
命令本身的完整路径,根据不同系统的需要更新脚本;或 - 使用您的脚本作为参数调用
node
命令。
另请参阅 this question (and my answer) 以了解有关 #!/usr/bin/env
技巧的更多讨论。
顺便说一句,在我的系统 (Linux Mint 17.2) 上,它安装为 /usr/bin/nodejs
。根据我的笔记,它在 Ubuntu 12.04 和 12.10 之间从 /usr/bin/node
变为 /usr/bin/nodejs
。 #!/usr/bin/env
技巧对此无济于事(除非您设置符号链接或类似的东西)。
更新:mtraceur 的评论说(重新格式化):
A workaround for the nodejs vs node problem is to start the file with the following six lines:
#!/bin/sh - ':' /*- test1=$(nodejs --version 2>&1) && exec nodejs "[=13=]" "$@" test2=$(node --version 2>&1) && exec node "[=13=]" "$@" exec printf '%s\n' "$test1" "$test2" 1>&2 */
This will first try
nodejs
and then trynode
, and only print the error messages if both of them are not found. An explanation is out of scope of these comments, I'm just leaving it here in case it helps anyone deal with the problem since this answer brought the problem up.
我最近没有使用 NodeJS。我希望 nodejs
与 node
问题在我首次发布此答案后的几年内得到解决。在 Ubuntu 18.04 上,nodejs
包安装 /usr/bin/nodejs
作为指向 /usr/bin/node
的符号链接。在一些较早的 OS(Ubuntu 或 Linux Mint,我不确定是哪个)上,有一个 nodejs-legacy
包提供 node
作为符号链接nodejs
。不能保证我的所有细节都正确。
Linux 内核的 exec
系统调用本机理解 shebangs (#!
)
当你在 bash:
./something
在 Linux 上,这会使用路径 ./something
.
exec
系统调用
内核的这一行在传递给 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 解释器确实看到文件中的 #!
行,但它必须被编程为忽略该行,即使 #
通常不是文件中的有效注释字符节点(不同于许多其他语言,例如 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. This mechanism even supports handlers by file extension. Another application is to transparently run executables of a different architecture with QEMU.
我不认为 POSIX specifies shebangs however: https://unix.stackexchange.com/a/346214/32558 ,尽管它确实在基本原理部分中以“如果系统支持可执行脚本,则可能会发生某些事情”的形式提到。然而,macOS 和 FreeBSD 似乎也实现了它。
PATH
搜索动机
shebang 存在的一大动机可能是在 Linux 中,我们经常希望 运行 来自 PATH
的命令,就像:
basename-of-command
而不是:
/full/path/to/basename-of-command
但是,如果没有 shebang 机制,Linux 怎么知道如何启动每种类型的文件?
在命令中对扩展进行硬编码:
basename-of-command.js
或在每个解释器上执行 PATH 搜索:
node basename-of-command
是有可能的,但是如果我们决定将命令重构为另一种语言,这会导致一切都崩溃。
Shebangs 很好地解决了这个问题。