怎么#!shebang工作?

moc*_*bin 43 unix shell scripting shebang

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

据我所知,该#字符表示注释的开始,该行应该被执行脚本的程序忽略.看起来,第一行是在某些时候通过某种东西读取,以便脚本由适当的程序执行.

有人可以更多地了解一下#!吗?

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

Kev*_*nko 42

推荐阅读:

unix内核的程序加载器负责这样做.当exec()调用它时,它要求内核从其参数的文件加载程序.然后它将检查文件的前16位以查看它具有的可执行格式.如果它发现这些位是#!它将使用文件第一行的其余部分来查找它应该启动的程序,并且它提供它尝试启动的文件的名称(脚本)作为最后一个参数.口译员计划.

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

  • @KevinPanko:内核的程序加载器检查的总是正好是 16 位吗?如果“#!”前面有 UTF-8 或 UTF-16 BOM,会发生什么情况? (2认同)
  • @stakx是的,这是在Unicode之前发明的,此后一直没有更改。http://unicode.org/faq/utf_bom.html#bom5 (2认同)

sta*_*ica 9

简单地说:该家当(#!)线被读取的外壳(例如sh,bash等)操作系统的程序加载器.虽然它正式看起来像一个注释,但它是文件的前两个字节这一事实将整个文件标记为文本文件和脚本.该脚本将被传递给shebang之后的第一行提到的可执行文件.瞧!


稍微长一点的故事:想象一下,你有自己的脚本foo.sh,并设置了可执行的bit(x).该文件包含以下内容:

#!/bin/sh

# some script commands follow...:
# *snip*
Run Code Online (Sandbox Code Playgroud)

现在,在shell上输入:

> ./foo.sh
Run Code Online (Sandbox Code Playgroud)

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

请记住,您在shell进程中键入此内容(假设这是程序/bin/sh).因此,该输入必须由该程序处理.它将此行解释为命令,因为它发现在该行上输入的第一个内容是实际存在的文件的名称以及具有可执行位的集合.

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

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

如果脚本的目的地是,例如/bin/perl,Perl解释器(可选)必须做的就是查看shebang行是否真的提到了Perl解释器.如果没有,Perl解释器将知道它无法执行此脚本.如果确实在shebang行中提到了Perl解释器,它会读取脚本文件的其余部分并执行它.

  • 它不是看着这两个字节的shell,它是系统(程序加载器),是吗?无论你是否在shell中运行脚本,都会发生同样的事情. (7认同)
  • 可执行文件的前两个字节是指示应如何执行的幻数; 对于解释的脚本,前两个字节方便地对应于ASCII字符`#!` (4认同)
  • shebang不是由shell处理的,它由操作系统本身处理. (4认同)

Cir*_*四事件 9

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

当您进行bash操作时:

./something
Run Code Online (Sandbox Code Playgroud)

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

此行在传递给exec以下文件的内核中调用:https : //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 python和当前文件作为第一个参数进行另一个调用:

/usr/bin/env python /path/to/script.py
Run Code Online (Sandbox Code Playgroud)

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

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

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下运行的可执行二进制文件?

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

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

  • @Daniel,内核不能像“env”那样进行“PATH”解析和目录搜索,据我所知,没有根本原因。一个可能的理由是“如果可以从用户态完成某件事,那就从用户态完成”以减少内核的攻击面:https://unix.stackexchange.com/questions/11907/shebang-and-path (2认同)