为什么 pgrep 找不到通过 env 启动的脚本?

为什么 pgrep 找不到通过 env 启动的脚本?

例如:

$ cat foo.sh
#!/usr/bin/env bash
while true; do sleep 1 ; done
$ ./foo.sh &
$ pgrep foo.sh
$

对比:

$ cat bar.sh
#!/bin/bash
while true; do sleep 1 ; done
$ ./bar.sh &
$ pgrep bar.sh
21202

由以下命令启动的进程env bash显示在输出中ps aux

terdon 4203  0.0  0.0  26676  6340 pts/3    S    17:23   0:00 /bin/bash

而开头的/bin/bash显示为

terdon  9374  0.0  0.0  12828  1392 pts/3    S    17:27   0:00 /bin/bash ./bar.sh

这可能解释了为什么它第一个没有被 捕获pgrep。所以,问题是:

  • 为什么调用时不显示脚本名称env
  • 是否pgrep简单地解析 的输出ps
  • 有什么办法可以解决这个问题,以便pgrep可以向我显示通过 启动的脚本env吗?

答案1

问#1

为什么通过 env 调用时不显示脚本名称?

来自舍邦维基百科文章:

在类 Unix 操作系统下,当带有 shebang 的脚本作为程序运行时,程序加载器会将脚本初始行的其余部分解析为解释器指令;相反,将运行指定的解释程序,并将尝试运行脚本时最初使用的路径作为参数传递给它。

因此,这意味着脚本的名称是内核所知道的进程名称,但是在调用它之后,加载程序立即执行参数#!并将脚本的其余部分作为参数传递。

然而env不这样做。当它被调用时,内核知道脚本的名称,然后执行envenv然后搜索$PATH寻找可执行文件来执行。

然后env执行解释器。它不知道脚本的原始名称,只有内核知道这一点。此时env正在解析文件的其余部分并将其传递给刚刚调用的解释器。

问#2

pgrep 是否简单地解析 ps 的输出?

是的,有点。它调用ps正在使用的相同 C 库。它不仅仅是一个包装器ps

问#3

有什么办法可以解决这个问题,以便 pgrep 可以向我显示通过 env 启动的脚本吗?

我可以在输出中看到可执行文件的名称ps

$ ps -eaf|grep 32405
saml     32405 24272  0 13:11 pts/27   00:00:00 bash ./foo.sh
saml     32440 32405  0 13:11 pts/27   00:00:00 sleep 1

在这种情况下,您可以使用它pgrep -f <name>来查找可执行文件,因为它将搜索整个命令行参数,而不仅仅是可执行文件。

$ pgrep -f foo
32405

参考

答案2

pgrep不解析 的输出ps,它将查找/proc/<PID>/status/proc/<PID>/cmdline来查找给定模式的匹配。做一些跟踪:

getpid()                                = 6572
stat("/proc/self/task", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
openat(AT_FDCWD, "/proc", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 267 entries */, 32768)   = 6792
stat("/proc/1", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/1/status", O_RDONLY)        = 4
read(4, "Name:\tinit\nState:\tS (sleeping)\nT"..., 1023) = 750
close(4)                                = 0
open("/proc/1/cmdline", O_RDONLY)       = 4
read(4, "/sbin/init", 2047)             = 10
close(4)                                = 0
stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/2/status", O_RDONLY)        = 4
read(4, "Name:\tkthreadd\nState:\tS (sleepin"..., 1023) = 518
close(4)                                = 0
open("/proc/2/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0
stat("/proc/3", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/3/status", O_RDONLY)        = 4
read(4, "Name:\tksoftirqd/0\nState:\tS (slee"..., 1023) = 521
close(4)                                = 0
open("/proc/3/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0
stat("/proc/6", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
open("/proc/6/status", O_RDONLY)        = 4
read(4, "Name:\tmigration/0\nState:\tS (slee"..., 1023) = 519
close(4)                                = 0
open("/proc/6/cmdline", O_RDONLY)       = 4
read(4, "", 2047)                       = 0
close(4)                                = 0

默认情况下,pgrep仅匹配进程名称(在您的情况下为 bash)。从man pgrep

 -f     The pattern is normally only matched against the process name.
        When -f is set, the full command line is used.

因此,在您的情况下,如果您想显示通过 启动的脚本env,请尝试使用:

pgrep -f foo.sh

相关内容