首页 > 解决方案 > 在脚本中使用 pgrep 进行进程计数

问题描述

在我的 Raspbian 中,我使用了以下命令:

pgrep -c ^

我认为这个命令应该在不使用更习惯的方法的情况下对所有进程进行计数

ps -A --no-headers | wc -l

但是,当在脚本上运行 pgrep -c ^ 时(假设在登录时),我想将计数的正在运行的进程数减去 1,以便调用的 shell(比如“sh”)运行脚本(或脚本本身)不算作一个过程......我不知道你是否在关注我。然后我开始用 pgrep 命令做一些实验

这是我的脚本的一个例子:

#!/bin/sh
ps1=$(pgrep -c ^)
ps2=$(expr $(pgrep -c ^) - 1)    # minus 1
ps3=$(expr $(pgrep -c ^) - 2)    # minus 2
echo ${ps1}
echo ${ps2}                      # ps1 == ps2
echo ${ps3}

我得到了结果(就我而言):

112
112
111

a) 在脚本中,更改 ps1 和 ps2 的评估顺序没有任何区别。关键是 ps1 和 ps2 总是相等的(不知道为什么)。

b) 直接在提示符上输入相同的命令,如下所示:

$> pgrep -c ^; expr $(pgrep -c ^) - 1

显示(如预期):

111
110

c)但是如果我修改命令以使用 echo:

$> echo "$(pgrep -c ^) : $(expr $(pgrep -c ^) - 1)"

甚至改变评估顺序:

"$> echo "$(expr $(pgrep -c ^) - 1) : $(pgrep -c ^)"

在这两种情况下,就像在脚本中一样,我得到:

112 : 112

问题:

1)为什么在脚本/回显之外它按预期工作,但在脚本内部或内部和 echo/printf 值是相同的?

2)命令 pgrep -c ^ 是按我想做的那样计算正在运行的进程的正确命令吗?

谢谢

标签: grepsh

解决方案


这里的问题是您忽略了这样一个事实,即 shell 通过分叉自己来执行每个命令替换来创建一个单独的进程。

我承认贝壳的行为方式令人困惑。就像,以这个为例:

$ pstree $$
sh───pstree
$ echo $(pstree $$)
sh───pstree

尽管我在上面另有说明,但似乎sh没有创建额外的进程来执行命令替换。但是,这是一种误解。它实际上是分叉的,但由于只有一个命令$(),所以分叉的进程没有意义;pstree所以它只是通过调用 exec* 系列的函数来替换自己;即它变成了pstree
如果在终止后还有另一个命令要执行pstree,则分叉的进程必须保持活动状态。看:

$ echo $(pstree $$; :)
sh───sh───pstree

嵌套命令替换的扩展也可能令人困惑,所以让我们也澄清一下。

$ echo $(echo $(pstree $$))
sh───sh───pstree

对于上面的示例,由于每个命令替换仅包含一个命令,因此人们可能希望sh在进程树中只看到一个命令;但这不是它的工作原理。包含在$()其中的命令在为执行命令替换而创建的子 shell 中处理,而不是在父 shell 中处理。对于这种情况,这意味着;分叉的进程制作了自己的另一个副本,以便能够执行pstree和收集其输出,然后执行echo;即分叉的进程在运行时还活着,因此树中pstree的额外进程。sh

因此,不要处理这些异常(以及我不知道或不记得的可能其他异常),而是依赖您的 shell 的进程分组机制。喜欢:

pgrep -c ^
pgrep -cvg $$
echo $(pgrep -cvg $$)
echo $(echo $(pgrep -cvg $$))
echo $(echo $(echo $(pgrep -cvg $$)))

对我来说,它输出:

264
263
263
263
263

推荐阅读