为什么“ps ax”找不到没有“#!”的正在运行的 bash 脚本标头?

为什么“ps ax”找不到没有“#!”的正在运行的 bash 脚本标头?

当我运行这个脚本时,打算运行直到被杀死......

# foo.sh

while true; do sleep 1; done

...我无法使用以下方式找到它ps ax

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

...但是如果我只是将通用的“ #!”标头添加到脚本中...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

...然后可以通过相同的命令找到该脚本ps...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

为什么会这样呢?
这可能是一个相关的问题:我认为“ #”只是一个注释前缀,如果是这样,“ #! /usr/bin/bash”本身只不过是一个注释。但“ #!”是否比仅仅作为一条评论具有更重要的意义?

答案1

当当前的交互式 shell 是bash,并且您运行不带 -line 的脚本时#!bash将运行该脚本。该过程将在输出中显示ps axbash.

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

在另一个终端中:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

有关的:


手册的相关部分如下bash

如果由于文件不是可执行格式且文件不是目录而导致执行失败,假设它是一个 shell 脚本,包含 shell 命令的文件。 生成一个子shell来执行它。该子 shell 重新初始化自身,以便效果就好像调用了一个新的 shell 来处理脚本,但父级记住的命令位置(请参阅下面的“SHELL BUILTIN COMMANDS”下的散列)由子级保留。

如果程序是以 开头的文件#!,则第一行的其余部分指定该程序的解释器。 shell执行指定的解释器在本身不处理此可执行格式的操作系统上。 [...]

这意味着,当没有 -line时,./foo.sh在命令行上运行与在子 shell 中运行文件中的命令相同,即foo.sh#!

$ ( echo "$BASHPID"; while true; do sleep 1; done )

一条正确的#!-line 指向 例如/bin/bash,就像这样做

$ /bin/bash foo.sh

答案2

当 shell 脚本以 开头时#!,第一行对于 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 脚本,它会自行执行它。它如何执行此操作的详细信息取决于 shell。您可以通过添加脚本来了解发生的一些情况ps $$,还可以通过观察进程(strace -p1234 -f -eprocess其中 1234 是 shell 的 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作为参数传递的脚本路径进行调用。换句话说,当您从 dash 执行 shebangless 脚本时,它的行为就好像该脚本有一个带有#!/bin/sh. Mksh 和 zsh 的行为方式相同。

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh

答案3

在第一种情况下,脚本由当前 shell 中的分叉子进程运行。

您应该首先运行echo $$,然后查看一个以您的 shell 的进程 ID 作为父进程 ID 的 shell。

相关内容