Bash脚本错误地计算其自身实例的数量

4

我创建了一个 bash 脚本,用于计算自身启动的实例。

这是它的代码(在这个示例中,我展示了实例而不是使用 wc -l 计数):

#!/bin/bash
nb=`ps -aux | grep count_itself.sh`
echo "$nb"
sleep 20

(当然,我的脚本名为 count_itself.sh
在执行时,我期望它返回两行,但它返回了三行:
root@myserver:/# ./count_itself.sh
root    16225   0.0 0.0 12400   1176 pts/0  S+  11:46   0:00    /bin/bash ./count_itself.sh
root    16226   0.0 0.0 12408   564 pts/0   S+  11:46   0:00    /bin/bash ./count_itself.sh
root    16228   0.0 0.0 11740   932 pts/0   S+  11:46   0:00    grep count_itself.sh

使用 & 标志运行它(即在后台),并手动执行 ps -aux 部分,它返回两个,这正是我想要的:

root@myserver:/# ./count_itself.sh &
[1] 16233
root@myserver:/# ps -aux | grep count_itself.sh
root     16233  0.0  0.0  12408  1380 pts/0    S    11:48   0:00 /bin/bash ./count_itself.sh
root     16240  0.0  0.0  11740   944 pts/0    S+   11:48   0:00 grep --color=auto count_itself.sh

我的问题是:为什么在脚本中执行ps -aux命令会比预期多返回一行?
换句话说,为什么在我的第一个示例中创建了进程ID为16226的进程?
编辑(因为大多数人似乎误解了我的问题):
我想知道为什么bash执行会返回两个/bin/bash ./count_itself.sh实例,而不是为什么会返回grep count_itself.sh
第二次编辑:
当然,我正在寻找避免这种情况并使脚本只返回一次/bin/bash ./count_itself.sh的方法。

2
\ps -aux | grep count_itself.sh`是在子shell中执行的,也就是在一个子进程中执行。因此,执行您的脚本的shell被分叉,子进程然后运行ps -aux | grep count_itself.sh` 命令。 - Leon
我建议: nb=`ps -aux | grep [c]ount_itself.sh - Cyrus
2
当你执行 ps -aux | grep count_itself.sh 命令时,你的 grep 命令会将其本身也计算在内,就像是 count_itself 的一个实例。你需要改进你的正则表达式。 - Stargateur
@Cyrus?那是要做什么的?它返回相同的结果。 - roberto06
@Leon,你能在回答中详细阐述一下你的想法吗?我猜你走在了正确的轨道上,但是由于我对Shell编程还比较陌生,所以并没有完全理解你的推理。 - roberto06
显示剩余4条评论
4个回答

5
这是使用psgrep命令时的一个常见问题。
解决方案之一是在字符周围添加一些方括号。
nb=$(ps -aux | grep '[c]ount_itself.sh')

这意味着你的grep实例无法匹配自身,因为它的进程名和参数包含方括号,但它所匹配的模式中没有方括号。
正如评论中提到的那样,您应该在变量周围使用双引号以保留空格。
你似乎有两个相同的shell实例,这是因为命令替换在子shell中执行。有关仅显示父进程的详细信息,请参见此问题

我编辑了我的问题,有没有办法让 ps -aux 不返回子shell的执行? - roberto06
编辑以添加链接。 - Tom Fenech

2

进程替换要求父shell启动子shell,即在子shell中分叉并执行指定的命令。这是必要的,以便父shell不受封闭在$(...)中的脚本所做的任何环境更改(变量、当前工作目录、陷阱)的影响。

示例:

$ cat test.sh 
#!/bin/bash

a=1
b="$(a=2; echo abc)"
echo "a=$a"
echo "b=$b"
$ ./test.sh 
a=1           # Note that the variable 'a' preserved its value
b=abc

由于进程分支,您会看到脚本的额外实例。我认为您不可能可靠地从输出中消除这些不必要的进程,因为原则上脚本可以合法地启动自己的另一个实例(将作为子进程运行),并且您无法区分这两种情况。一个hacky(笨拙的)解决方案是让脚本在指定位置(例如/tmp/your_script_name)创建PID文件,并在终止时删除它。

2
我建议采用以下方法:
排除所有父进程是自己的进程:
 ps --pid $$ -N -a | grep count_itself.sh

这意味着显示所有父进程不是自己的命令(这排除了您的grep进程和fork进程执行计数器句子)。

仅使用 --pid $$ 标志时,它仍会回显两行内容,如果我添加 -N 标志,则不会回显任何内容。我正在寻找中间的结果:P - roberto06
我猜如果使用-N标志返回空值,那是因为你是唯一运行的实例,也许如果你想计算自己,可以将结果加1。 - Joan Esteban

1

最终找到了一种方法,虽然有些丑陋,但部分灵感来自@TomFenech在他的回答中提供的问题:

#!/bin/bash
nb=$(ps f | grep '[c]ount_itself.sh' | grep -v '    \\_')
echo "$nb"
sleep 20

执行:

root@myserver:/# ./count_itself.sh
17725 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh

在后台已经有一个正在运行的情况下执行:

root@myserver:/# ./count_itself.sh &
[1] 17733
root@myserver:/# ./count_itself.sh
17733 pts/1    S      0:00  \_ /bin/bash ./count_itself.sh
17739 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh

解释(据我所知):

  • ps f 返回活动进程的树形结构
  • grep '[c]ount_itself.sh' 限制前一个命令只显示 count_itself.sh 的实例

返回:

17808 pts/1    S+     0:00  \_ /bin/bash ./count_itself.sh
17809 pts/1    S+     0:00      \_ /bin/bash ./count_itself.sh
  • grep -v ' \\_' 排除包含4个空格(相当于制表符)和\_的行,这些行对应于子进程。

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