节点文件开头的“/usr/bin/env节点”到底是做什么的?

What exactly does "/usr/bin/env node" do at the beginning of node files?

我在 nodejs 中的一些示例的开头看到了这一行 #!/usr/bin/env node,我用 google 搜索但没有找到任何可以回答该行原因的主题。

单词的性质使搜索变得不那么容易。

我最近读了一些 javascriptnodejs 书,但我不记得在其中任何一本书中看到过它。

如果你想要一个例子,你可以看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 nodeshebang 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。我希望 nodejsnode 问题在我首次发布此答案后的几年内得到解决。在 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 很好地解决了这个问题。