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

Sto*_*row 13 shell ps shell-script shebang

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

# foo.sh

while true; do sleep 1; done
Run Code Online (Sandbox Code Playgroud)

...我无法找到它使用ps ax

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh
Run Code Online (Sandbox Code Playgroud)

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

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

while true; do sleep 1; done
Run Code Online (Sandbox Code Playgroud)

...然后脚本可以通过相同的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
Run Code Online (Sandbox Code Playgroud)

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

Kus*_*nda 14

当当前交​​互式 shell 是bash,并且您运行一个没有#!-line的脚本时,bash将运行该脚本。该过程将在ps ax输出中显示为bash.

$ cat foo.sh
# foo.sh

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

$ ./foo.sh
55411
Run Code Online (Sandbox Code Playgroud)

在另一个终端:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash
Run Code Online (Sandbox Code Playgroud)

有关的:


相关章节构成bash手册:

如果此执行失败,因为文件不是可执行格式,并且文件不是目录,则假定它是 shell script,一个包含 shell 命令的文件。 产生一个子shell来执行它。这个子shell重新初始化自己,所以效果就像一个新的shell被调用来处理脚本,除了父级记住的命令的位置(参见下面SHELL BUILTIN COMMANDS下的哈希)由子级保留。

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

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

$ ( echo "$BASHPID"; while true; do sleep 1; done )
Run Code Online (Sandbox Code Playgroud)

适当的线#!指向 eg /bin/bash,它就像做

$ /bin/bash foo.sh
Run Code Online (Sandbox Code Playgroud)


Gil*_*il' 12

当 shell 脚本以 开头时#!,就 shell 而言,第一行是注释。然而,前两个字符对系统的另一部分有意义:内核。这两个字符#!被称为shebang。要了解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 -eprocess1234 是 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
Run Code Online (Sandbox Code Playgroud)

相反,DashENOEXEC通过调用/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
Run Code Online (Sandbox Code Playgroud)

  • @StoneThrow 与其说子bash“知道原始命令行参数”,不如说它不修改它们。程序可以修改 `ps` 作为命令行参数报告的内容,但仅限于一点:它必须修改现有的内存缓冲区,它不能扩大这个缓冲区。因此,如果 bash 尝试修改它的 `argv` 以添加脚本的名称,它并不总是有效。子进程不会“传递参数”,因为子进程中从来没有“execve”系统调用。它只是保持运行的同一个 bash 进程映像。 (2认同)