准确地说,“shebang”只是#! 这两个字符。当 Unix 系统上的文件作为可执行文件被调用时(最终通过系统调用 execve),内核查看它的前几个字节以确定它是什么种类的可执行文件。如果这些字节将其识别为包含机器代码,那么内核会将机器代码加载到内存中并导致 CPU 开始执行它。如果机器代码是从 C 程序编译的,它的main 函数最终会被调用。 (如果您想了解此 过程是如何工作的,请阅读 John Levine 所著的“Linkers and Loaders”一书。)
但如果前两个字节是 # 和 !(ASCII 值 35 和 33),那么内核将改为扫描文件的第一行以查找 解释器的名称,然后它将运行解释器,提供#! 程序的名称作为命令行参数。 (有关内核如何解析第一行的详细信息,请参阅this answer。)如果你这样做了
./foo.js a b c d
而 foo.js 以 #! /usr/bin/node 开头,那么内核的行为就像使用参数向量调用了 execve
/usr/bin/node ./foo.js a b c d
它会打开文件/usr/bin/node,发现这是一个机器码可执行文件,然后继续加载机器码,它是节点解释器,并运行它。 Node 的 main 函数会注意到它的第一个参数是 ./foo.js,它会打开该文件并将其作为 Javascript 程序执行,而不是进入其交互式读取-评估-打印循环。
Node 解释器本身会忽略#! 行——但它的解析器中必须有代码才能忽略它;内核不会将其过滤掉。对于 Unix 上常用的许多解释语言(sh、awk、perl、python、ruby,...),cmets 从# 运行到行尾,所以这会自动发生;事实上,在过去,#! 符号被选中,因为 sh cmets 是从# 到行尾的。 Javascript cmets 不能这样工作,因此 Node 必须在文件开头有一个 #! 的特殊情况。
您显示的#! 行具有额外的间接级别:#! /usr/bin/env node 使内核使用参数向量运行程序/usr/bin/env(同样由机器代码组成)
/usr/bin/env node ./foo.js a b c d
env 然后看到它的第一个参数是node,它沿着搜索路径 查找名为node 的程序的可执行文件。搜索路径由环境变量定义:type
echo $PATH
在您的提示下了解它是什么。这是一个以冒号分隔的目录列表。例如,PATH 的常用值是
/usr/local/bin:/usr/bin:/bin
表示依次在目录/usr/local/bin、/usr/bin 和/bin 中查找程序;换句话说,使用 PATH 的值和上面的参数,env 将首先尝试运行
/usr/local/bin/node ./foo.js a b c d
如果这不起作用,它会尝试/usr/bin/node 等等。如果你不知道 Node 解释器(或其他)安装在哪里,这个额外的间接是必要的,因为内核的 #! 处理将只接受 #! 之后的绝对路径名;它不会为您进行 PATH 搜索。如果您确实知道node 的安装位置,最好直接编写该路径名,这样您的程序的行为就不会取决于调用用户的PATH 是什么(例如some Linux distributions used to use the name /usr/bin/node for a completely unrelated program,所以如果您有#! /usr/bin/env node 和用户在他们的 PATH 上没有 /opt/node-1.9/bin 之前的 /usr/bin,会引起欢闹)。
我为execve 和以#! 开头的文件描述的行为不是 specified by POSIX(在该页面上提到,但仅在非规范的基本原理部分)。但是,它在您现在可能遇到的所有类 Unix 操作系统中都是一致的。我完全不知道它到底有多大。