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


13

当我运行此脚本时,该脚本将一直运行直到被杀死...

# 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”本身仅是注释。但是,“ #!”具有的意义远不只是评论吗?


您正在使用什么Unix?
库沙兰丹

@Kusalananda-Linux linuxbox 3.11.10-301.fc20.x86_64#1 SMP 12月5日14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

Answers:


13

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

$ 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在本身不处理此可执行格式的操作系统上执行指定的解释器。[...]

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

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

适当的线#!指向eg /bin/bash,这是在做

$ /bin/bash foo.sh

我想我是遵循的,但是您所说的在第二种情况下也是正确的:bash在第二种情况下也运行脚本,当ps脚本显示为“ /usr/bin/bash ./foo.sh” 时可以观察到。因此,正如您所说的那样,在第一种情况下,bash将运行脚本,但是与第二种情况一样,该脚本是否不需要“传递”到派生的bash可执行文件中?(如果是这样,我想可以用管道将其找到...?)
StoneThrow

@StoneThrow查看最新答案。
库萨兰南达

“ ...,除了获得一个新进程外”-嗯,无论哪种方式,您都会得到一个新进程,只是$$在子shell情况(echo $BASHPID/ bash -c 'echo $PPID')中仍然指向旧进程。
Michael Homer

@MichaelHomer啊,谢谢!将更新。
库沙兰丹

12

当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 -eprocess1234为外壳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

很棒的答案。一个问题RE:您解释的后备实现:我想既然子节点bash是派生的,它就可以访问与argv[]其父节点相同的数组,这就是它如何知道“当您最初调用bash时传递的原始命令行参数”,以及因此,这就是为什么未将子脚本作为显式参数传递给子脚本的原因(因此,为什么grep无法找到该子脚本)-这样准确吗?
StoneThrow

1
您实际上可以关闭内核的shebang行为(该BINFMT_SCRIPT模块可以控制此行为,并且可以将其删除/模块化,尽管它通常是静态链接到内核中的),但是我看不到您为什么要这么做,除非是在嵌入式系统中。作为解决此问题的方法,请bash使用配置标志(HAVE_HASH_BANG_EXEC)进行补偿!
ErikF

2
@StoneThrow子bash并不“知道原始的命令行参数”,因为它不会修改它们。程序可以修改ps报告为命令行参数的内容,但只能做到以下几点:它必须修改现有的内存缓冲区,而不能扩大该缓冲区。因此,如果bash尝试修改其argv名称以添加脚本的名称,则它并不总是有效。子代不会“传递参数”,因为execve子代永远不会有系统调用。它只是不断运行的bash进程映像。
吉尔斯(Gillles)“所以-别再邪恶了”

-1

在第一种情况下,该脚本由您当前shell中的分支孩子运行。

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

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.