pgrep 数出来的进程数总是多一个?先看自匹配这条坑
现象
用 pgrep 统计某个脚本有没有在跑,结果总比实际多一个。或者反过来:pkill 执行完,本该被杀的那个进程还在,别的东西被误杀了。
这不是 pgrep 的 bug,是自匹配:你写的这条 pgrep 命令,它自己的命令行里就含有你要匹配的那个关键词,于是它把自己也算进去了。
一个最小复现:
pgrep -af run-daily.sh
输出里会看到类似这样的两行:
1234567 bash run-daily.sh
1234999 bash -c pgrep -af run-daily.sh
第二行就是执行这条命令的 shell 本身。如果把它接进 wc -l 做判断:
pgrep -f run-daily.sh | wc -l
在"其实没在跑"的情况下也会返回 1,于是脚本以为任务正在运行,跳过执行——这是最典型的误判形态。
为什么 -f 更容易踩
两种匹配模式的区别:
写法 | 匹配范围 | 自匹配风险
pgrep 名字 | 只比对进程名(comm,通常 15 字符截断) | 低,因为 shell 的进程名是 bash
pgrep -f 字符串 | 比对整条命令行 | 高,因为你的命令自己就含有这个字符串
所以只要用了 -f,并且关键词出现在你自己这条命令里,就必然自匹配。
三种规避写法
一、方括号技巧(最通用)
把关键词的首字母写成字符类:
pgrep -f "[r]un-daily.sh"
对方括号在正则里代表"匹配 r 这个字符",所以它匹配的真实进程名是 run-daily.sh;而这条命令自己的命令行里写的是 [r]un-daily.sh,字面上不等于 run-daily.sh,于是不自匹配。
这个技巧对所有"在命令行里搜自己"的场景都通用,例如:
ps aux | grep "[n]ginx"
二、用 pidof 或 systemctl(更稳)
如果不是必须按命令行匹配,优先用不依赖字符串比对的接口:
pidof run-daily.sh
systemctl is-active 你的服务名
systemctl is-active 返回 active / inactive / activating,是状态机口径,不会自匹配,也不会因为进程名截断而漏判。
三、排除自身 PID
需要精确控制时,把当前 shell 的 PID 及其父进程排除掉:
pgrep -f run-daily.sh | grep -v -e "^$$\$" -e "^$PPID\$"
实际用起来比前两种麻烦,只在脚本里确实要拿进程列表做后续处理时才值得。
顺带一个同源坑:pkill 也会自匹配
pkill -f 关键词 同样会把自己的命令行纳入候选。更麻烦的是,pkill 杀到自己的父 shell 时,脚本会在半路莫名其妙退出——表现为"命令执行了,但后面的代码没跑"。
所以 pkill 更推荐用精确目标:
pkill -f "[r]un-daily.sh"
或者干脆用服务管理:
systemctl stop 你的服务名
判断清单
- 数字总比预期大 1 → 先怀疑自匹配,把命令换成方括号写法再数。
- 在 ssh 远程执行里数不对 → 远程那层 shell 的命令行也会被匹配,同样处理。
- 进程名超过 15 字符 → 不加
-f可能因截断匹配不上,此时必须用-f,那就要防自匹配。 - 拿
pgrep | wc -l当"是否在跑"的判据 → 换成systemctl is-active或pgrep -c加方括号写法。
小结
pgrep -f / pkill -f / ps | grep 这三个组合都会自匹配,原因是命令行里含有被搜索的字面量。最省事的解法就是方括号写法:[r]un-daily.sh。如果本来就有服务管理器,用 systemctl is-active 比数进程更可靠。
- 点赞
- 收藏
- 关注作者
评论(0)