“/ usr / bin / env node”在节点文件的开头究竟做了什么?

时间:2015-11-03 21:55:08

标签: node.js shebang

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

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

我最近阅读了一些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);
});

有人可以解释一下这条线的含义是什么吗?

如果我放入或删除此行有什么区别?在什么情况下我需要它?

4 个答案:

答案 0 :(得分:105)

#!/usr/bin/env node shebang line 的一个实例:类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 的一部分,并安装CLI根据{{​​3}}的值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 answerenv'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”位通过在修改后的环境中运行脚本并且更可靠地找到解释程序,使程序更具跨平台性。

您不一定需要它,但最好使用它来确保可移植性(和专业性)