当shell脚本以开头时#!
,就外壳而言,第一行是注释。但是,前两个字符对系统的另一部分有意义:内核。这两个字符#!
称为“ 射棒”。要了解shebang的作用,您需要了解程序的执行方式。
从文件执行程序需要内核采取措施。这是execve
系统调用的一部分。内核需要验证文件许可权,释放与当前在调用进程中运行的可执行文件关联的资源(内存等),为新的可执行文件分配资源以及将控制权转移到新程序(以及更多我不会说)。该execve
系统调用替换当前运行的进程的代码; 有一个单独的系统调用fork
来创建新流程。
为此,内核必须支持可执行文件的格式。该文件必须包含以内核理解的方式组织的机器代码。Shell脚本不包含机器代码,因此无法以这种方式执行。
shebang机制允许内核将解释代码的任务推迟到另一个程序。当内核看到可执行文件以开头时#!
,它将读取接下来的几个字符,并将文件的第一行(减去前导#!
和可选空格)解释为另一个文件的路径(加上参数,在此不再赘述) )。当告诉内核执行文件时/my/script
,如果内核看到文件以该行开头#!/some/interpreter
,则内核/some/interpreter
将使用参数执行/my/script
。然后/some/interpreter
决定/my/script
要执行的脚本文件。
如果文件既没有包含内核能够理解的格式的本机代码,又没有以shebang开头的怎么办?好吧,那么该文件不可执行,并且execve
系统调用将失败,并显示错误代码ENOEXEC
(可执行格式错误)。
这可能是故事的结局,但是大多数shell都实现了后备功能。如果内核返回ENOEXEC
,则Shell将查看文件的内容并检查它是否看起来像Shell脚本。如果Shell认为该文件看起来像Shell脚本,则它自己执行该文件。具体操作方式取决于外壳。您可以通过添加ps $$
脚本来查看正在发生的事情,并通过查看以strace -p1234 -f -eprocess
1234为外壳PID 的进程来了解更多情况。
在bash中,此后备机制是通过调用fork
而不是通过实现的execve
。bash子进程将自行清除其内部状态,并打开新的脚本文件以运行它。因此,运行脚本的过程仍然使用原始bash代码映像和原始调用bash时传递的原始命令行参数。ATT ksh的行为相同。
% bash --norc
bash-4.3$ ./foo.sh
PID TTY STAT TIME COMMAND
21913 pts/2 S+ 0:00 bash --norc
相反,Dash ENOEXEC
通过调用/bin/sh
脚本的路径作为参数来做出反应。换句话说,当您从破折号执行一个shebangless脚本时,它的行为就像该脚本的shebang行带有#!/bin/sh
。Mksh和zsh的行为相同。
% dash
$ ./foo.sh
PID TTY STAT TIME COMMAND
21427 pts/2 S+ 0:00 /bin/sh ./foo.sh