unix # !shebang是如何工作的?

xjreopfe  于 6个月前  发布在  Unix
关注(0)|答案(3)|浏览(76)

在一个脚本中,你必须在第一行包含一个#!,然后是将执行脚本的程序的路径(例如:sh,perl)。
据我所知,#字符表示注解的开始,这一行应该被执行脚本的程序忽略。看起来,这第一行在某个时候被某些东西读取,以便脚本被适当的程序执行。
有没有人能更清楚地说明#!的工作原理?
我真的很好奇,所以答案越深入越好。

eqoofvh9

eqoofvh91#

推荐阅读:

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

3npbholx

3npbholx2#

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

当你在bash上做的时候:

./something

字符串
在Linux上,这将调用路径为./somethingexec系统调用。
在传递给exec的文件的内核中调用这一行:https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

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


它读取文件的第一个字节,并将它们与#!进行比较。
如果比较结果为真,则Linux内核将解析该行的其余部分,并使用路径/usr/bin/env python和当前文件作为第一个参数进行另一个exec调用:

/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)。让我们通过阅读/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,尽管它在基本原理部分提到了,并且以“如果系统支持可执行脚本,可能会发生一些事情”的形式。

2vuwiymt

2vuwiymt3#

**简短的故事:**shebang(#!)行由shell(例如shbash等)读取,shell是操作系统的程序加载器。虽然它看起来像一个注解,但它是文件的前两个字节,这一事实将整个文件标记为文本文件和脚本。脚本将被传递到shebang后的第一行中提到的可执行文件。瞧!
**稍微长一点的故事:**假设你有你的脚本,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;它只是处理脚本文件本身的其余部分。)
如果脚本的目的地是/bin/perl,Perl解释器所要做的(可选)就是查看shebang行是否真的提到了Perl解释器。如果没有,Perl解释器就知道它不能执行这个脚本。如果确实在shebang行提到了Perl解释器,它就读取脚本文件的其余部分并执行它。

相关问题