当我在shell中执行文件时,会发生什么情况?


32

所以,我以为我对此有很好的了解,但是只是进行了一次测试(以回应我不同意某人的对话),发现我的理解是有缺陷的...

尽可能详细地说明我在Shell中执行文件时会发生什么情况?我的意思是,如果我键入以下内容:如果./somefile some arguments在外壳中键入内容,然后按return键(somefile并存在于cwd中,并且我具有的读取和执行权限somefile),那么在幕后会发生什么?

以为答案是:

  1. Shell进行syscall exec,将路径传递给somefile
  2. 内核检查somefile并查看文件的幻数,以确定它是否是处理器可以处理的格式
  3. 如果幻数表示文件是处理器可以执行的格式,则
    1. 创建一个新流程(在流程表中有一个条目)
    2. somefile被读取/映射到内存。创建一个堆栈,执行跳转到的代码的入口点somefile,并ARGV初始化为参数数组(a char**["some","arguments"]
  4. 如果幻数是shebang,exec()如上所述产生一个新进程,但是使用的可执行文件是shebang引用的解释器(例如/bin/bash/bin/perl),somefile并传递给STDIN
  5. 如果文件没有有效的幻数,则发生类似“无效文件(不良幻数):执行格式错误”的错误

但是有人告诉我,如果文件是纯文本,那么Shell会尝试执行命令(就像我键入一样bash somefile)。我不相信这一点,但我只是尝试了一下,这是正确的。因此,我显然对这里实际发生的事情有一些误解,并且想了解其机制。

当我在shell中执行文件时,会发生什么情况?(尽可能多的细节是合理的...)


没有什么完全替代的方法可以查看源代码以进行全面的了解。
通配符

1
@Wildcard实际上就是我现在正在做的,:-)如果可以的话,我会回答我自己的问题
乔什

1
source somefile不过,与所产生的新过程有很大的不同./somefile
thrig

@thrig是的,我同意。但是我认为,如果文件中没有魔术数字,那./somefile不会导致bash执行命令somefile。我认为它只会显示一个错误,而实际上似乎有效source somefile
Josh

我又错了,我可以确认如果somefile是文本文件,那么如果我尝试执行它,就会生成一个新的shell。echo $$如果我执行vs源文件,文件的行为会有所不同。
乔什

Answers:


31

关于Linux上“程序如何运行”的明确答案是LWN.net上的两篇文章,令人惊讶的是,如何运行程序如何运行程序:ELF二进制文件。第一篇文章简要介绍了脚本。(严格来说,最终答案在源代码中,但是这些文章更易于阅读,并提供了源代码的链接。)

一个小小的实验表明,您几乎完全正确,并且包含shell的简单命令列表文件的执行需要由shell处理。的调用execve(2)手册页包含用于测试程序的源代码,execve的; 我们将用它来查看没有外壳的情况。首先,编写一个测试脚本testscr1,其中包含

#!/bin/sh

pstree

另一个testscr2,仅包含

pstree

使它们都可执行,并验证它们是否都从外壳运行:

chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less

现在,使用execve(假设您在当前目录中构建了它)再试一次:

./execve ./testscr1
./execve ./testscr2

testscr1仍然运行,但testscr2产生

execve: Exec format error

这表明外壳处理方式testscr2不同。尽管它不处理脚本本身,但仍然可以/bin/sh用来执行脚本。这可以通过管道来验证testscr2less

./testscr2 | less -ppstree

在我的系统上,我得到

    |-gnome-terminal--+-4*[zsh]
    |                 |-zsh-+-less
    |                 |     `-sh---pstree

如您所见,有一个我正在使用的外壳程序zsh,它启动了less,还有另一个外壳程序shdash在我的系统上)简单地运行脚本,该脚本程序运行了pstree。在in中zshzexecvein 处理Src/exec.c:shell用于execve(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);
        }

bash具有相同的行为,execute_cmd.c并通过有用的注释实现(如taliezin所指出的):

执行一个简单的命令,希望该命令在某个磁盘文件中定义。

  1. fork ()
  2. 连接管道
  3. 查找命令
  4. 进行重定向
  5. execve ()
  6. 如果execve失败,请查看文件是否设置了可执行模式。如果是这样,并且它不是目录,则将其内容作为外壳脚本执行。

POSIX定义一组功能,被称为exec(3)功能,它包裹execve(2)并提供此功能太; 有关详细信息,请参见muru的答案。在Linux上,至少这些功能由C库而不是内核实现。


这太棒了,并且有我想要的详细信息,谢谢!
乔什(Josh)2013年

12

在某种程度上,这取决于所使用的特定exec系列功能。execve正如史蒂芬·基特Stephen Kitt)详细显示的那样,仅以正确的二进制格式运行文件或以正确的shebang开头的脚本。

然而execlpexecvp走一步:如果家当不正确,该文件与执行/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.)

这在某种程度上受到POSIX(强调我)的支持:

标准开发人员指出的一个潜在的混乱来源是过程映像文件的内容如何影响exec系列功能的行为。以下是已采取的措施的说明:

  1. 如果过程映像文件是此系统的有效可执行文件(格式为可执行,有效且具有适当特权),则系统将执行该文件。

  2. 如果过程映像文件具有适当的特权,并且具有对该系统而言可执行但无效的格式(例如,已识别的另一种体系结构的二进制文件),则这是一个错误,并且errno设置为[EINVAL](请参阅稍后的RATIONALE)在[EINVAL]上)。

  3. 如果过程映像文件具有适当的特权,但不能以其他方式识别:

    1. 如果这是对execlp()或execvp()的调用,则他们将假定过程映像文件是Shell脚本,则调用命令解释器。

    2. 如果这不是对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.)

但是,AFAICT并没有普遍使用execlpexecvp直接使用外壳,大概是为了更好地控制环境。它们都使用来实现相同的逻辑execve


3
我还想补充一点,至少在Linux上,execlexeclpexecleexecvexecvpexecvpe都是前端的execve系统调用; 前者由C库提供,内核仅知道execve(以及execveat现在)。
史蒂芬·基特

@StephenKitt这就解释了为什么我找不到这些功能的手册页上man7.org的部分2
穆鲁

6

这可能是斯蒂芬·基特(Stephen Kitt)回答的补充,作为bash文件中来源的注释execute_cmd.c

执行一个简单的命令,希望该命令在某个磁盘文件中定义。

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.  

如果是这样,并且它不是目录,则将其内容作为外壳脚本执行。


0

它作为shell脚本执行,不是源程序(例如,在执行的文件中设置的变量不会影响外部)。大概是有雾的过去,当时只有一种 shell和一种可执行格式。不是可执行文件,它必须是Shell脚本。


2
您误解了我的问题。详细发生什么至少,我需要了解对shebang的检查是什么,exec()还是外壳?我想要更多内部部件
乔什(Josh)2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.