Linux内核的exec系统调用可以原生地理解shebangs (#!
当你在bash上执行时:
./something
在Linux上,它调用exec系统调用,路径为。/something。
内核的这行代码在传递给exec的文件https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25上被调用
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
它读取文件的第一个字节,并将它们与#!进行比较。
如果比较为真,那么Linux内核将解析该行的其余部分,并以path /usr/bin/python3和current file作为第一个参数进行另一次exec调用:
/usr/bin/python3 /path/to/script.py
这适用于任何使用#作为注释字符的脚本语言。
类似地,如果你决定使用env,你可能总是应该在python3位于不同位置的系统上工作,特别是pyenv,还可以参阅这个问题,shebang:
#!/usr/bin/env python3
最后类比地调用:
/usr/bin/env python3 /path/to/script.py
它完成了你对env python3的期望:在PATH中搜索python3并运行/usr/bin/python3 / 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)。让我们通过读取ELF可执行文件/bin/ls的前4个字节来确认:
head -c 4 "$(which ls)" | hd
输出:
00000000 7f 45 4c 46 |.ELF|
00000004
因此,当内核看到这些字节时,它会获取ELF文件,将其正确地放入内存中,并使用它启动一个新进程。请参见:内核如何获得在linux下运行的可执行二进制文件?
最后,您可以使用binfmt_misc机制添加自己的shebang处理程序。例如,您可以为.jar文件添加一个自定义处理程序。这种机制甚至支持按文件扩展名的处理程序。另一个应用程序是使用QEMU透明地运行不同体系结构的可执行文件。
但是我不认为POSIX指定了shebangs: https://unix.stackexchange.com/a/346214/32558,尽管它在基本原理部分提到了,并且以“如果系统支持可执行脚本,可能会发生一些事情”的形式出现。macOS和FreeBSD似乎也实现了它。
PATH搜索动机
shebangs存在的一个重要动机可能是,在Linux中,我们经常希望从PATH运行命令,就像这样:
basename-of-command
而不是:
/full/path/to/basename-of-command
但是,如果没有shebang机制,Linux如何知道如何启动每种类型的文件呢?
在命令中硬编码扩展:
basename-of-command.py
或者在每个解释器上执行PATH搜索:
python3 basename-of-command
这是一种可能性,但这有一个主要的问题,如果我们决定将命令重构为另一种语言,那么一切都会崩溃。
她完美地解决了这个问题。
参见:为什么人们要写#!/usr/bin/env python脚本的第一行?