为什么 ps o/p 在管道后列出 grep 进程?

27

当我执行时

$ ps -ef | grep cron

我明白了

root      1036     1  0 Jul28 ?        00:00:00 cron
abc    21025 14334  0 19:15 pts/2    00:00:00 grep --color=auto cron
我的问题是为什么我看到第二行。据我所知,ps列出进程并将列表传输到grepps正在列出进程时,grep甚至还没有开始运行,那么为什么grep进程会显示在输出中?

相关的第二个问题:

当我执行

$ ps -ef | grep [c]ron

我只得到

root      1036     1  0 Jul28 ?        00:00:00 cron

第一次和第二次执行grep有什么区别?


17
您会对 sleep 3 | sleep 3 | sleep 3 的结果感到惊奇的;-) - Alfe
7个回答

41

当你执行以下命令时:

ps -ef | grep cron

你正在使用的shell会调用pipe()函数创建一个FIFO(命名管道),然后它会fork()(生成自身的运行副本)。这将创建一个新的子进程。这个新生成的子进程将关闭其标准输出文件描述符(fd 1),并将fd 1附加到父进程(您执行命令的shell)创建的管道的写入端。这是可能的,因为fork()系统调用将为每个进程维护一个有效的打开文件描述符(在这种情况下是管道fd)。接着它将使用exec()调用第一个(在您的例子中)出现在PATH环境变量中的ps命令。使用exec()调用后,此进程将成为您执行的命令。

所以现在您有了一个具有子进程的shell进程,在您的情况下,子进程是带有-ef属性的ps命令。

此时,父进程(shell)再次进行fork()。这个新生成的子进程close()它的标准输入文件描述符(fd 0),并将fd 0附加到父进程(您执行命令的shell)创建的管道的读取端。

接着它将使用exec()调用第一个(在您的例子中)出现在PATH环境变量中的grep命令。

现在,您有了具有两个子进程(兄弟)的shell进程,其中第一个是带有-ef属性的ps命令,第二个是带有cron属性的grep命令。管道的读取端附加到grep命令的STDIN上,写入端附加到ps命令的STDOUT上:即ps命令的标准输出附加到grep命令的标准输入。

由于ps命令会在标准输出上发送每个运行进程的信息,而grep命令会在其标准输入上获取与给定模式匹配的东西,所以您将得到对第一个问题的答案:

  1. shell运行:ps -ef;
  2. shell运行:grep cron;
  3. psgrep发送数据(甚至包含字符串"grep cron")
  4. grepSTDIN匹配其搜索模式,并且由于您传递给grep的"cron"属性,它匹配到了字符串"grep cron",因为在grep开始执行时,"grep cron"是ps返回的字符串之一。

当您执行:

ps -ef | grep '[c]ron'
属性传递给 grep 命令,指示它匹配包含 "c" 后跟 "ron" 的内容。这类似于第一个示例,但这将破坏由 ps 返回的匹配字符串,因为:
  1. shell 运行:ps -ef;
  2. shell 运行:grep [c]ron;
  3. ps 发送数据(甚至包含字符串 grep [c]ron)到 grep
  4. grep 无法从标准输入流中匹配其搜索模式,因为找不到包含 "c" 后跟 "ron" 的字符串,但它已经找到了一个包含 "c" 后跟 "]ron" 的字符串。
GNU grep 没有任何字符串匹配限制,在某些平台上(例如 Solaris、HPUX 和 aix),字符串的限制由 "$COLUMN" 变量或终端屏幕宽度给出。
希望这个长答案可以澄清 shell 管道过程的一些问题。
提示:
ps -ef | grep cron | grep -v grep

感谢您详细说明了@Ben Jackson的答案。 - Ankur Agarwal
1
我认为运行这个命令将是对这个优秀答案的一个很好的演示: $ ps aux | grep grep | grep grep | grep grep | grep grep。你会看到四行grep grep - 全部都在你创建的管道中。 - Esmu Igors

9
Shell使用一系列的fork()、pipe()和exec()调用来构建您的管道。根据不同的shell,任何部分都可能先被构建。所以,grep甚至可以在ps之前就开始运行。或者即使ps先开始,它仍将写入一个4k内核管道缓冲区,并最终阻塞(在打印进程输出的同时),直到grep启动并开始消耗管道中的数据。在后一种情况下,如果ps能够在grep开始之前启动并完成,您可能在输出中看不到grep cron。您可能已经注意到了这种不确定性。

如果ps被阻塞了,那么列表中就不会包含grep..对吧。但是Ignacio似乎在暗示grep必须正在运行。我有点困惑。 - Ankur Agarwal
我实际上没有看到你提到的不确定性!但是注意到这一点非常有趣。 - Ankur Agarwal

8

在您的命令中

ps -ef | grep 'cron'

Linux在执行"grep"命令之前先执行ps -ef命令。然后,Linux将ps -ef的标准输出(STDOUT)映射到grep命令的标准输入(STDIN)中。它不会执行ps命令,将结果存储在内存中,然后将其传递给grep。想一想,为什么要这样做呢?想象一下,如果你要传输100GB的数据呢?
关于您的第二个问题:在grep(和大多数正则表达式引擎)中,您可以指定方括号,让它知道您将接受括号内的任何字符。因此,编写[c]表示它将接受任何字符,但仅指定了c。同样,您可以进行任何其他字符的组合。
ps aux | grep cron
root      1079  0.0  0.0  18976  1032 ?        Ss   Mar08   0:00 cron
root     23744  0.0  0.0  14564   900 pts/0    S+   21:13   0:00 grep --color=auto cron

^ 这个符号与它自己匹配,因为你的命令中包含了 "cron" 字符串。

ps aux | grep [c]ron
root      1079  0.0  0.0  18976  1032 ?        Ss   Mar08   0:00 cron

这与cron匹配,因为cron包含一个c,其余的是"ron"。但它不符合您的请求,因为您的请求是[c]ron。

您可以在括号中放置任何内容,只要其中包含c:

ps aux | grep [cbcdefadq]ron
root      1079  0.0  0.0  18976  1032 ?        Ss   Mar08   0:00 cron

如果移除了 C,就不会匹配了,因为 "cron" 以 c 开头。
ps aux | grep [abedf]ron

^ 没有结果

第二次编辑

重申一下,您可以使用grep执行各种疯狂的操作。选择第一个字符并无特殊意义。

ps aux | grep [c][ro][ro][n]
root      1079  0.0  0.0  18976  1032 ?        Ss   Mar08   0:00 cron

我刚刚为问题增加了一个部分。我意识到“赏金注释”很难阅读。谢谢。 - Ankur Agarwal
Ben Jackson(下面)似乎暗示ps可能在grep之前运行并将数据写入内核管道。 - Ankur Agarwal
据我所知,哪个先启动并不重要。操作系统不会立即分配任何CPU时间给它们中的任何一个,直到PS的STDOUT被映射到GREP的STDIN为止。 - GoldenNewby
你需要引号。如果你在/bin或任何其他目录中运行带有名为cron(或corn或任何其他匹配项)的文件的命令,则在grep开始之前,c[ro][ro][n]将被更改为cron。相比之下,'c[ro][ro][n]'不会被扩展。如果您在启用了nullglobfailglob选项的shell中运行原始未引用的命令,则情况会变得更加混乱。 - Charles Duffy

3
您写道:“根据我的理解,ps列出进程并将列表传输到grep。grep甚至在ps列出进程时都没有开始运行。”
您的理解是不正确的。
这不是管道的工作方式。Shell 不会先完全运行第一个命令,记住第一个命令的输出,然后在使用该数据作为输入后运行下一个命令。相反,两个进程同时执行,它们的输入/输出被连接起来。正如 Ben Jackson 所写,如果两个进程都非常短暂,并且内核可以轻松地管理通过连接传递的少量数据,那么就没有什么特别的保证进程会同时运行。在这种情况下,它确实可能按照您的期望发生,只是偶然发生而已。但需要牢记的概念模型是它们同时运行。
如果您想要官方来源,那么可以看看 bash man 页面:
  A pipeline is a sequence of one or more commands separated by the character |.  The format for a pipeline is:

         [time [-p]] [ ! ] command [ | command2 ... ]

  The  standard  output  of command is connected via a pipe to the standard input of command2.  This connection is
  performed before any redirections specified by the command (see REDIRECTION below).

  ...

  Each command in a pipeline is executed as a separate process (i.e., in a subshell).

关于你的第二个问题(实际上与主题无关,我很抱歉),你只是描述了正则表达式如何工作的一个特性。正则表达式cron匹配字符串cron。正则表达式[c]ron不会匹配字符串[c]ron。因此,第一个grep命令将在进程列表中找到自己,但第二个命令不会。


1

其他人已经回答了你的实际问题,但我想提供一个提示:如果你想避免看到列出的grep进程,可以这样做:

$ ps -ef | grep [c]ron

谢谢,但我对下面的答案还有进一步的疑问。请看我的评论。 - Ankur Agarwal
为什么使用grep [c]ron不会列出grep进程,而使用grep cron总是列出grep进程?中括号表达式的作用是什么?能否详细说明一下? - Ankur Agarwal
3
它能正常运行是因为 grep 正则表达式精确匹配了 c 后面跟着的 ron,但 ps 输出会显示出实际输入的命令 grep [c]ron。因此,grep 的表达式并不会匹配它并将其过滤掉。 - Michael Berkowski
这需要更多的引用; 将其改为 grep '[c]ron',否则如果您在包含名为 cron 的文件的目录中运行此命令,它将变成 grep cron(因为 shell 会将任何看起来像 glob 的东西替换为它扩展到的文件列表... 如果你很幸运并且它是默认设置; 使用 nullglob 它将只成为没有参数的 grep,使用 failglob 它将成为一个错误)。 - Charles Duffy

0

pgrep 有时比 ps -ef | grep word 更好,因为它排除了 grep。请尝试使用。

pgrep -f bash
pgrep -lf bash

-3
$ ps -ef | grep cron

Linux Shell 总是从右到左执行命令。因此,在执行 ps -ef 之前,grep cron 已经被执行了,这就是为什么输出显示的是命令本身。

$ ps -ef | grep [c]ron

但是在这里,您指定了grep ron,然后只有c。因此,输出没有命令行,因为命令中有[c]ron。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接