【问题标题】:Why does ps o/p list the grep process after the pipe?为什么 ps o/p 会在管道之后列出 grep 进程?
【发布时间】:2011-10-17 03:32:29
【问题描述】:

当我这样做时

$ 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 列出了进程并将列表通过管道传输到grepgrep 甚至还没有开始运行,而 ps 正在列出进程,那么 grep 进程怎么会在 o/p 中列出呢?

相关第二个问题:

当我这样做时

$ ps -ef | grep [c]ron

我只得到

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

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

【问题讨论】:

  • 你会对sleep 3 | sleep 3 | sleep 3 的结果感到惊讶;-)

标签: linux bash pipe ps


【解决方案1】:

当你执行命令时:

ps -ef | grep cron

你正在使用的外壳

(...我假设你的情况是 bash,由于 grep 的颜色属性,我认为你正在运行一个像 linux 发行版这样的 gnu 系统,但它在其他 unix/shell 上也是一样的......)

将执行pipe() 调用来创建一个FIFO,然后它会fork()(创建一个自身的运行副本)。这将创建一个新的子进程。这个新生成的子进程将close() 其标准输出文件描述符(fd 1)并将fd 1 附加到由父进程(执行命令的shell)创建的管道的写入端。这是可能的,因为fork() 系统调用将为每个维护一个有效的打开文件描述符(在本例中为管道 fd)。这样做之后,它将exec() 第一个(在您的情况下)ps 命令在您的PATH 环境变量中找到。通过exec() 调用,该进程将成为您执行的命令。

因此,您现在拥有一个带有子进程的 shell 进程,在您的情况下,它是具有 -ef 属性的 ps 命令。

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

这样做之后,它将在您的 PATH 环境变量中找到exec() 第一个(在您的情况下)grep 命令。

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

由于编写ps 是为了发送每个正在运行的进程的标准输出信息,而编写grep 是为了在其标准输入上获取必须与给定模式匹配的内容,因此您将得到第一个问题的答案:

  1. shell 运行:ps -ef;
  2. shell 运行:grep cron;
  3. ps 将数据(甚至包含字符串“grep cron”)发送到 grep
  4. grep 匹配来自STDIN 的搜索模式,并且它匹配字符串“grep cron”,因为您传递给grep 的“cron”属性:您正在指示grep 匹配“cron”字符串并且这是因为“grep cron”是psgrep 开始执行时返回的字符串。

当你执行时:

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 的回答。
  • 我认为运行这将很好地说明这个精彩的答案:$ ps aux | grep grep | grep grep | grep grep | grep grep。您将看到四行 grep grep – 您创建的管道中的所有行。
【解决方案2】:

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

【讨论】:

  • 如果 ps 阻止,则列表将不包含 grep..正确。但 Ignacio 似乎建议 grep 必须运行。我有点困惑。
  • 我实际上没有看到你提到的非确定性!但值得注意的是。
【解决方案3】:

在你的指挥下

ps -ef | grep 'cron'

Linux 在 ps -ef 命令之前执行“grep”命令。然后 Linux 将“ps -ef”的标准输出(STDOUT)映射到 grep 命令的标准输入(STDIN)。

它不执行 ps 命令,将结果存储在内存中,然后将其传递给 grep。想一想,为什么会这样?想象一下,如果您正在传输 100 GB 的数据?

编辑关于您的第二个问题:

在 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

^ 没有结果

编辑 2

重申这一点,你可以用 grep 做各种疯狂的事情。选择第一个字符来执行此操作没有任何意义。

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

【讨论】:

  • 我只是在问题中添加了一个部分。我突然想到,很难阅读“赏金笔记”。谢谢。
  • Ben Jackson(下)似乎暗示 ps 可能在 grep 之前运行并将数据写入内核管道。
  • 据我所知,哪个先开始并不重要。在 PS 的 STDOUT 映射到 GREP 的 STDIN 之前,操作系统不一定会为它们中的任何一个分配任何 CPU 时间。
【解决方案4】:

您写道:“据我了解, ps 列出进程并将列表通过管道传输到 grep。当 ps 列出进程时,grep 甚至还没有开始运行”。

你的理解不正确。

管道不是这样工作的。 shell运行第一个命令完成,记住第一个命令的输出,然后之后使用该数据作为输入运行下一个命令。不会。相反,两个进程都在执行,并且它们的输入/输出是连接的。正如 Ben Jackson 所写,没有什么可以特别保证进程同时运行,如果它们都非常短暂,并且内核可以轻松地管理通过连接传递的少量数据。在那种情况下,它真的可能以你期望的方式发生,只是偶然。但要记住的概念模型是它们并行运行。

如果你想要官方资源,bash 手册页怎么样:

  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 命令会在进程列表中找到自己,但第二个不会。

【讨论】:

    【解决方案5】:

    您的实际问题已被其他人回答,但我会提供一个提示:如果您不想看到列出的 grep 进程,您可以这样做:

    $ ps -ef | grep [c]ron
    

    【讨论】:

    • 谢谢,但我对下面的答案有进一步的疑问。请看我的cmets。
    • 为什么使用 grep [c]ron 不列出 grep 进程,而使用 grep cron 总是列出 grep 进程?括号表达式的作用是什么。能详细点吗?
    • @abc 之所以有效,是因为grep 正则表达式与c 后跟ron 完全匹配,但ps 输出将按字面意思显示grep [c]ron,因为这是输入的命令。所以grep的表达式不匹配,过滤掉。
    • 这需要更多的引用;使其成为grep '[c]ron',否则如果您在包含名为cron 的文件的目录中运行它,它将变为grep cron(因为shell 将任何看起来像glob 的东西替换为它扩展为的文件列表...... . 如果你很幸运并且它是默认设置;使用nullglob,它会变成不带参数的grep,使用failglob,它会变成一个错误。
    【解决方案6】:

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

    pgrep -f bash
    pgrep -lf bash
    

    【讨论】:

      【解决方案7】:
      $ ps -ef | grep cron
      

      Linux Shell 总是从右到左执行命令。所以,在 ps -ef 执行之前 grep cron 已经执行,这就是为什么 o/p show 是命令本身。

      $ ps -ef | grep [c]ron
      

      但是在这个你指定 grep ron 后跟只有 c。所以,o/p 没有命令行,因为在命令中有 [c]ron。

      【讨论】:

        猜你喜欢
        • 2021-07-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-16
        • 1970-01-01
        相关资源
        最近更新 更多