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

Gep*_*ser 85 shebang node.js

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

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

我最近读了一些javascriptnodejs书,我不记得在其中任何一个看过它.

如果你想要一个例子,你可以看到RabbitMQ官方教程,他们几乎在所有的例子中都有它,这里有一个:

#!/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);
});
Run Code Online (Sandbox Code Playgroud)

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

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

mkl*_*nt0 117

#!/usr/bin/env node一个shebang行的实例:类似Unix的平台上的可执行纯文本文件中的第一行,通过魔术#!前缀后面的命令行(称为shebang)告诉系统将该文件传递给执行的解释器.

注:的Windows没有支持家当线,所以他们有效地忽略了那里.在Windows上,它只是给定文件的文件扩展名,用于确定可执行文件将对其进行解释.但是,你仍然需要它们npm.[1]

以下对shebang行的一般讨论仅限于类Unix平台:

在下面的讨论中,我将假设包含由Node.js执行的源代码的文件只是命名file.

  • 需要此行,如果你要调用一个Node.js的源文件,直接,因为它本身就是一个可执行文件-这是假定该文件已被标记为可执行的命令,例如chmod +x ./file,然后可以让您调用文件例如,./file或者,如果它位于$PATH变量中列出的某个目录中,则简单地表示为file.

    • 具体来说,您需要一个shebang行来创建基于Node.js源文件的CLI作为npm 包的一部分,并npm根据"bin"package.json文件中的键值安装CLI .也看到这个答案,了解它如何与全局安装的包一起使用.脚注[1]显示了如何在Windows上处理它.
  • 不需要此行通过node解释器显式调用文件,例如,node ./file


可选背景信息:

#!/usr/bin/env <executableName>是一种可移植地指定解释器的方法:简而言之,它表示:执行<executableName>在您(首先)在$PATH变量中列出的目录中找到它的任何地方(并隐式地将其传递给手头文件的路径).

这解释了这样一个事实:给定的解释器可能安装在跨平台的不同位置,这绝对node是Node.js二进制文件的情况.

相比之下,env实用程序本身的位置可以依赖于跨平台的相同位置,即/usr/bin/env- 并且在shebang行中需要指定可执行文件的完整路径.

请注意,POSIX实用程序env在此处重新调整用于按文件名定位并在中执行可执行文件$PATH.
真正的目的env是管理命令的环境 - 参见envPOSIX规范Keith Thompson的有用答案.


值得注意的是Node.js正在为shebang行做出语法异常,因为它们不是有效的JavaScript代码(#不是JavaScript中的注释字符,不像POSIX类shell和其他解释器).


[1]为了跨平台一致性,在安装包文件中指定的可执行文件(通过属性)时,在Windows上npm创建包装 *.cmd文件(批处理文件).基本上,这些包装批处理文件模仿 Unix shebang功能:它们使用shebang行中指定的可执行文件显式调用目标文件 - 因此,即使您只打算在Windows上运行它们,您的脚本也必须包含一个shebang行 - 请参阅此答案我的细节. 由于可以在没有扩展名的情况下调用文件,因此可以实现无缝的跨平台体验:在Windows和Unix上,您可以通过其原始的无扩展名称有效地调用已安装的CLI. package.json"bin"
*.cmd.cmdnpm

  • @AndrewLam:在Windows上,诸如.cmd和.py之类的文件扩展名确定将使用什么程序来执行此类文件。在Unix上,shebang行执行该功能。为了使`npm`在所有支持的平台上都可以使用,即使在Windows上也需要使用shebang行。 (3认同)

Kei*_*son 26

由解释器执行的脚本通常在顶部有一个shebang行告诉操作系统如何执行它们.

如果您有一个名为foo第一行的脚本#!/bin/sh,系统将读取该第一行并执行等效的/bin/sh foo.因此,大多数解释器都设置为接受脚本文件的名称作为命令行参数.

后面的解释器名称#!必须是完整的路径; 操作系统不会搜索你$PATH找到解释器.

如果你有一个要执行的脚本node,写第一行的显而易见的方法是:

#!/usr/bin/node
Run Code Online (Sandbox Code Playgroud)

但如果node未安装该命令,则无效/usr/bin.

一个常见的解决方法是使用env命令(这是没有真正用于这一目的):

#!/usr/bin/env node
Run Code Online (Sandbox Code Playgroud)

如果调用foo了脚本,操作系统将执行相同的操作

/usr/bin/env node foo
Run Code Online (Sandbox Code Playgroud)

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技巧的更多讨论.

顺便说一句,在我的系统(Linux Mint 17.2)上,它安装为/usr/bin/nodejs.根据我的记录,从改变/usr/bin/node/usr/bin/nodejs的Ubuntu 12.04和12.10之间.这个#!/usr/bin/env技巧无济于事(除非你设置一个符号链接或类似的东西).


Cir*_*四事件 6

execLinux内核的系统调用原生理解shebangs ( #!)

当你在 bash 上执行以下操作时:

./something
Run Code Online (Sandbox Code Playgroud)

在 Linux 上,这将exec使用路径调用系统调用./something

内核的这一行在传递到的文件上被调用exechttps://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
Run Code Online (Sandbox Code Playgroud)

它读取文件的第一个字节,并将它们与#!.

如果比较为真,则该行的其余部分将由 Linux 内核解析,并再次调用exec

  • 可执行文件:/usr/bin/env
  • 第一个参数:node
  • 第二个参数:脚本路径

因此相当于:

/usr/bin/env node /path/to/script.js
Run Code Online (Sandbox Code Playgroud)

env是一个可执行文件,它搜索PATH例如 find /usr/bin/node,然后最终调用:

/usr/bin/node /path/to/script.js
Run Code Online (Sandbox Code Playgroud)

Node.js 解释器确实可以看到#!文件中的该行,但必须对其进行编程以忽略该行,即使它通常#不是 Node 中的有效注释字符(与许多其他语言(例如 Python)不同),另请参阅:井号 (#) 作为注释在 JavaScript 中开始?

是的,您可以使用以下命令进行无限循环:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a
Run Code Online (Sandbox Code Playgroud)

Bash 识别出错误:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links
Run Code Online (Sandbox Code Playgroud)

#!只是恰好是人类可读的,但这不是必需的。

如果文件以不同的字节开始,则exec系统调用将使用不同的处理程序。另一个最重要的内置处理程序是针对 ELF 可执行文件的:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305,它检查字节7f 45 4c 46(这也恰好是人类的)可读.ELF)。让我们通过读取 的前 4 个字节来确认这一点/bin/ls,它是一个 ELF 可执行文件:

head -c 4 "$(which ls)" | hd 
Run Code Online (Sandbox Code Playgroud)

输出:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 
Run Code Online (Sandbox Code Playgroud)

因此,当内核看到这些字节时,它会获取 ELF 文件,将其正确放入内存中,并用它启动一个新进程。另请参阅:内核如何获取在Linux下运行的可执行二进制文件?

最后,您可以使用该机制添加您自己的 shebang 处理程序binfmt_misc。例如,您可以.jarfiles添加自定义处理程序。该机制甚至支持按文件扩展名进行处理。另一个应用是使用 QEMU 透明地运行不同架构的可执行文件

然而,我不认为POSIX指定了 shebangs:https: //unix.stackexchange.com/a/346214/32558,尽管它确实在基本原理部分中提到,并且以“如果系统支持可执行脚本,某些东西可能会发生”。然而 macOS 和 FreeBSD 似乎也实现了它。

PATH搜索动机

Shebangs 存在的一大动机很可能是在 Linux 中,我们经常希望从PATH以下位置运行命令:

basename-of-command
Run Code Online (Sandbox Code Playgroud)

代替:

/full/path/to/basename-of-command
Run Code Online (Sandbox Code Playgroud)

但是,如果没有 shebang 机制,Linux 如何知道如何启动每种类型的文件呢?

在命令中对扩展进行硬编码:

 basename-of-command.js
Run Code Online (Sandbox Code Playgroud)

或在每个解释器上实现 PATH 搜索:

node basename-of-command
Run Code Online (Sandbox Code Playgroud)

这是一种可能性,但这有一个主要问题,如果我们决定将命令重构为另一种语言,一切都会崩溃。

Shebangs 完美地解决了这个问题。


归档时间:

查看次数:

38803 次

最近记录:

6 年,5 月 前