57

在脚本中,您必须#!在第一行包含a,后跟将执行脚本的程序的路径(例如:sh、perl)。

据我所知,该#字符表示注释的开头,并且该行应该被执行脚本的程序忽略。看起来,第一行在某些时候被某些东西读取,以便脚本由正确的程序执行。

有人可以更详细地说明 的工作原理#!吗?

我对此非常好奇,所以答案越深入越好。

4

3 回答 3

59

推荐阅读:

unix 内核的程序加载器负责执行此操作。当exec()被调用时,它要求内核从文件的参数中加载程序。然后它将检查文件的前 16 位以查看它具有什么可执行格式。如果它发现这些位是#!,它将使用文件第一行的其余部分来查找它应该启动哪个程序,并提供它试图启动的文件的名称(脚本)作为最后一个参数解释程序。

然后解释器正常运行,并将 视为#!注释行。

于 2010-06-09T19:37:39.837 回答
23

Linux 内核exec系统调用使用初始字节#!来识别文件类型

当你在 bash 上做:

./something

在 Linux 上,这会exec使用 path 调用系统调用./something

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

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

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

如果比较结果为真,则该行的其余部分将由 Linux 内核解析,并exec以路径/usr/bin/env python和当前文件作为第一个参数进行另一次调用:

/usr/bin/env python /path/to/script.py

这适用于任何#用作注释字符的脚本语言。

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

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)。让我们通过读取 的前 4 个字节来确认/bin/ls,这是一个 ELF 可执行文件:

head -c 4 "$(which ls)" | hd 

输出:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

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

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

我不认为POSIX指定shebangs但是:https ://unix.stackexchange.com/a/346214/32558 ,虽然它确实在基本原理部分提到,并且以“如果系统支持可执行脚本的形式”发生”。然而,macOS 和 FreeBSD 似乎也实现了它。

于 2016-12-02T18:37:01.273 回答
10

短篇小说: shebang ( ) 行由操作系统的程序加载程序的外壳程序(例如,等)#!读取。虽然它在形式上看起来像注释,但它是文件的前两个字节这一事实将整个文件标记为文本文件和脚本。该脚本将被传递给 shebang 之后第一行中提到的可执行文件。瞧!shbash


稍微长一点的故事:假设你有你的脚本 ,设置foo.sh了可执行位 ( x)。该文件包含例如以下内容:

#!/bin/sh

# some script commands follow...:
# *snip*

现在,在你的 shell 上,你输入:

> ./foo.sh

编辑:请在阅读以下内容之后或之前阅读以下评论!事实证明,我错了。显然,将脚本传递给目标解释器的不是 shell,而是操作系统(内核)本身。

请记住,您在 shell 进程中输入了这个(假设这是程序/bin/sh)。因此,该输入必须由该程序处理。它将这一行解释为一个命令,因为它发现在该行上输入的第一件事是一个实际存在的文件的名称,并且该文件设置了可执行位。

/bin/sh然后开始读取文件的内容并在文件的#!最开头发现 shebang ( )。对于 shell,这是一个令牌(“幻数”),它通过它知道文件包含一个脚本。

现在,它如何知道脚本是用哪种编程语言编写的呢?毕竟,您可以执行 Bash 脚本、Perl 脚本、Python 脚本……到目前为止,shell 所知道的只是它正在查看一个脚本文件(不是二进制文件,而是一个文本文件)。因此它读取下一个输入直到第一个换行符(这将导致/bin/sh, 与上面的比较)。这是脚本将被传递到执行的解释器。(在这种特殊情况下,目标解释器是 shell 本身,因此它不必为脚本调用新的 shell;它只处理脚本文件本身的其余部分。)

如果脚本的目标是 eg /bin/perl,那么 Perl 解释器(可选地)要做的就是查看 shebang 行是否真的提到了 Perl 解释器。如果不是,Perl 解释器就会知道它不能执行这个脚本。如果在 shebang 行中确实提到了 Perl 解释器,它会读取脚本文件的其余部分并执行它。

于 2010-06-09T19:32:20.733 回答