Jos*_*osh 38 shell kernel exec shebang
所以,我以为我对此有一个很好的理解,但只是进行了一个测试(以回应我不同意某人的对话)并发现我的理解有缺陷......
尽可能详细地说明当我在 shell 中执行文件时到底发生了什么?我的意思是,如果我输入:./somefile some arguments进入我的 shell 并按回车键(并且somefile存在于 cwd 中,并且我有读取+执行权限somefile),那么引擎盖下会发生什么?
我以为答案是:
exec,将路径传递给somefilesomefile并查看文件的幻数,以确定它是否是处理器可以处理的格式somefile被读取/映射到内存。创建堆栈并执行跳转到代码的入口点somefile,并ARGV初始化为参数数组 (a char**, ["some","arguments"])exec()如上所述生成一个新进程,但使用的可执行文件是shebang(例如/bin/bash或/bin/perl)引用的解释器并somefile传递给STDIN但是有人告诉我,如果文件是纯文本,那么 shell 会尝试执行命令(就像我输入了bash somefile)。我不相信这一点,但我只是尝试了一下,结果是正确的。所以我显然对这里实际发生的事情有一些误解,并想了解机制。
当我在我的 shell 中执行一个文件时到底发生了什么?(尽可能多的细节是合理的......)
Ste*_*itt 36
Linux 上“程序如何运行”的最终答案是LWN.net上的两篇文章,标题是,令人惊讶的是,程序如何运行和程序如何运行:ELF 二进制文件。第一篇文章简要介绍了脚本。(严格来说,最终答案在源代码中,但这些文章更易于阅读并提供源代码链接。)
一个小实验表明你几乎做对了,并且包含一个简单命令列表的文件的执行需要由 shell 处理,没有 shebang。所述的execve(2)手册页包含用于测试程序的源代码,的execve; 我们将使用它来看看没有外壳会发生什么。首先,编写一个测试脚本,testscr1,包含
#!/bin/sh
pstree
Run Code Online (Sandbox Code Playgroud)
和另一个,testscr2,只包含
pstree
Run Code Online (Sandbox Code Playgroud)
使它们都可执行,并验证它们都从 shell 运行:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Run Code Online (Sandbox Code Playgroud)
现在再试一次,使用execve(假设你在当前目录中构建了它):
./execve ./testscr1
./execve ./testscr2
Run Code Online (Sandbox Code Playgroud)
testscr1仍在运行,但testscr2产生
execve: Exec format error
Run Code Online (Sandbox Code Playgroud)
这表明 shell 的处理方式testscr2不同。虽然它不处理脚本本身,但它仍然用来/bin/sh这样做;这可以通过管道testscr2来验证less:
./testscr2 | less -ppstree
Run Code Online (Sandbox Code Playgroud)
在我的系统上,我得到
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | `-sh---pstree
Run Code Online (Sandbox Code Playgroud)
如您所见,我使用的 shell 是zsh启动的less,另一个 shell 是普通的sh(dash在我的系统上)来运行脚本,它运行pstree. 在zsh这由zexecvein处理Src/exec.c:shellexecve(2)用来尝试运行命令,如果失败,它会读取文件以查看它是否有 shebang,并相应地处理它(内核也会这样做),如果失败,它会尝试使用 运行文件sh,只要它没有从文件中读取任何零字节:
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
Run Code Online (Sandbox Code Playgroud)
bash具有相同的行为,execute_cmd.c通过有用的评论实现(如taliezin所指出的):
执行一个简单的命令,希望在某个地方的磁盘文件中定义。
fork ()- 连接管道
- 查找命令
- 做重定向
execve ()- 如果
execve失败,请查看文件是否设置了可执行模式。如果是这样,并且它不是目录,则将其内容作为 shell 脚本执行。
POSIX定义一组功能,被称为的exec(3)功能,它包裹execve(2)并提供此功能太; 有关详细信息,请参阅muru的答案。在 Linux 上,至少这些函数是由 C 库实现的,而不是由内核实现的。
mur*_*uru 12
在某种程度上,这取决于所使用的特定exec族函数。execve,正如Stephen Kitt详细展示的那样,只运行正确的二进制格式的文件或以正确的 shebang 开头的脚本。
然而,execlp和execvp走一步:如果家当不正确,该文件与执行/bin/sh在Linux上。来自man 3 exec:
Special semantics for execlp() and execvp()
The execlp(), execvp(), and execvpe() functions duplicate the actions
of the shell in searching for an executable file if the specified
filename does not contain a slash (/) character.
…
If the header of a file isn't recognized (the attempted execve(2)
failed with the error ENOEXEC), these functions will execute the
shell (/bin/sh) with the path of the file as its first argument. (If
this attempt fails, no further searching is done.)
Run Code Online (Sandbox Code Playgroud)
这在某种程度上得到了POSIX 的支持(重点是我的):
标准开发人员指出的一个潜在混淆源是进程映像文件的内容如何影响 exec 系列函数的行为。以下是对所采取措施的描述:
如果进程映像文件是该系统的有效可执行文件(以可执行且有效且具有适当权限的格式),则系统执行该文件。
如果进程映像文件具有适当的权限并且其格式对于该系统可执行但无效(例如其他体系结构的可识别二进制文件),则这是一个错误并且 errno 设置为 [EINVAL](参见后面的 RATIONALE [EINVAL])。
如果过程映像文件具有适当的权限但无法以其他方式识别:
如果这是对 execlp() 或 execvp() 的调用,则它们会调用命令解释器,假定进程映像文件是 shell 脚本。
如果这不是对 execlp() 或 execvp() 的调用,则会发生错误并且将 errno 设置为 [ENOEXEC]。
这没有指定如何获得命令解释器,因此,但没有指定必须给出错误。因此,我猜 Linux 开发人员允许运行此类文件/bin/sh(或者这已经是一种常见做法,他们只是效仿)。
FWIW,FreeBSD 联机帮助页exec(3)也提到了类似的行为:
Some of these functions have special semantics.
The functions execlp(), execvp(), and execvP() will duplicate the actions
of the shell in searching for an executable file if the specified file
name does not contain a slash ``/'' character.
…
If the header of a file is not recognized (the attempted execve()
returned ENOEXEC), these functions will execute the shell with the path
of the file as its first argument. (If this attempt fails, no further
searching is done.)
Run Code Online (Sandbox Code Playgroud)
然而,AFAICT 没有使用execlp或execvp直接使用通用 shell ,大概是为了更好地控制环境。它们都使用execve.
这可能是 Stephen Kitt 答案的补充,作为bash文件中源的评论execute_cmd.c:
执行一个简单的命令,希望在某个地方的磁盘文件中定义。
Run Code Online (Sandbox Code Playgroud)1. fork () 2. connect pipes 3. look up the command 4. do redirections 5. execve () 6. If the execve failed, see if the file has executable mode set.如果是这样,并且它不是目录,则将其内容作为 shell 脚本执行。