1. pgrep到底解决了什么问题
在Linux下查进程,很多人的第一反应是 ps aux | grep xxx。这套组合拳确实能用,但用久了你就会发现它有几个很别扭的地方:grep出来的结果里经常带着grep自身那条进程;想拿到纯PID还得再套一层awk;如果进程名带特殊字符,正则匹配的坑一个接一个。我早期写运维脚本时,为处理这些边角问题不知道多写了多少行shell。
pgrep这个命令就是来解决这些痛点的。它的工作方式和其他进程查询命令不同:直接遍历内核的进程表,按名字、用户、终端、父进程ID等条件去匹配,然后把符合条件的进程PID直接输出到标准输出,没有额外信息干扰。从操作逻辑上看,它省掉了“先看一大片进程快照、再人工筛选”的中间步骤,输出的结果天然适合丢给其他命令做下一步处理。
它的实用价值主要体现在几个场景:脚本里需要快速判断某个服务是否在运行、需要拿到一组进程PID做批量操作(比如结束进程、发送信号)、需要在几十个同名进程里精确找出特定条件下的那一个。日常排查问题时,pgrep 配合 pkill 能省下大量重复劳动,这也是为什么在很多系统管理的教科书和面试题里,pgrep都是绕不开的基础命令。
另外,pgrep是procps工具包的一部分,和 ps、pkill、top、free 这些命令出自同一个项目,所以在绝大多数主流Linux发行版里都是预装的,不需要额外安装。Debian/Ubuntu系里它属于 procps 包,CentOS/RHEL/Fedora系里属于 procps-ng 包,基本开箱即用,这也是它能在生产环境里被广泛依赖的原因之一。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基本用法:从一条命令到一组命令的配合
2.1 最简单的按名字匹配
pgrep最基础的用法就是直接接一个进程名:
bash复制$ pgrep nginx
32045
32046
32047
这里匹配的是进程名(comm字段),不是完整命令行。如果你在系统里跑了一个Java应用,启动参数特别长,进程名是 java,那 pgrep java 就能把相关的Java进程都列出来,这个场景在面试题里很常见。
有一点要特别注意:pgrep的进程名匹配是正则表达式匹配,不是字符串精确匹配。这意味着 pgrep ssh 能匹配到 sshd,pgrep ssh-agent 也会被匹配进来,因为正则里 ssh 能命中所有包含 ssh 子串的进程名。想精确匹配,得用 -x 参数。
2.2 精确匹配用 -x
-x 表示“精确匹配”,要求进程名必须和给定模式完全一致才算命中。
bash复制$ pgrep -x sshd
2345
如果不加 -x,pgrep ssh 的结果就完全不一样了:
bash复制$ pgrep ssh
2345 # sshd
6789 # ssh-agent
10233 # ssh(如果有的话)
在实际运维中,这个区别能避免很多误判。比如脚本里要判断sshd是否在运行,用 pgrep -x sshd 就比 pgrep ssh 要可靠得多,因为后者可能因为系统里存在其他名字带ssh的进程而给出错误结论。
2.3 按完整命令行匹配用 -f
进程名只有15个字符的显示限制(Linux内核的comm字段最长15字节),所以很多长命令的“进程名”看起来不直观。比如Python脚本:
bash复制$ python3 /opt/apps/worker.py --config /etc/worker.conf
这个进程的comm字段是 python3,用 pgrep python3 能命中,但如果系统里有多个不同的Python脚本在跑,你就分不清谁是谁了。这时候需要 -f 参数,让它匹配完整的命令行:
bash复制$ pgrep -f "worker.py --config"
-f 会遍历 /proc/PID/cmdline 里的完整参数来进行正则匹配,精确度大幅提升。在管理Python、Java、Node这类“一个解释器跑多个脚本”的场景里,-f 几乎是标配。我自己写系统服务管理脚本时,判断服务存活性基本都以 pgrep -f 为准。
这里有一个常见的坑:如果匹配模式写得太宽泛,-f 可能把pgrep自己的命令也匹配进去。比如:
bash复制$ pgrep -f pgrep
这条命令很可能输出两个PID,一个是正在运行的pgrep进程本身,另一个是它fork出来的子进程。这个问题后面章节会专门展开讲。
2.4 按用户筛选用 -u
多用户系统或者容器环境里,经常需要只看某个用户跑的进程。-u 按有效用户ID或用户名过滤,用法很直观:
bash复制$ pgrep -u nginx
也可以同时指定多个用户:
bash复制$ pgrep -u root,nginx
配合 -f 一起用,可以精确到“某个用户的某条命令”:
bash复制$ pgrep -u www-data -f "php-fpm"
这种组合在排查Web服务进程时特别方便。生产环境里Nginx的worker进程通常归nginx用户管,PHP-FPM的进程归www-data或者自定义用户管,一个组合命令就能把两者分开看清楚。
2.5 只看最新或最旧的进程用 -n / -o
当你匹配到一堆进程,想只挑其中一个时,-n(newest,最新创建)和 -o(oldest,最旧创建)就派上用场了。
bash复制$ pgrep -n -u www-data php-fpm
这条命令返回www-data用户下最新创建的那个php-fpm进程PID。这在reload PHP-FPM或者Nginx时特别有用——可以用这个PID去查最新worker的启动时间和状态。
-o 反过来,返回最老的那个进程。比如系统里有一组worker进程,你想找master进程,通常master是最先启动的,pgrep -o -u nginx nginx 就能大概率命中master。
2.6 按父进程ID筛选用 -P
-P 按父进程PID过滤子进程,这个参数在排查“某个父进程下有哪些子进程”时很有价值:
bash复制$ pgrep -P 32045
上面这条命令会列出父进程PID为32045的所有子进程。写监控脚本时,我想知道一个服务的worker数量,就可以用 pgrep -P <master_pid> | wc -l 来做统计。
2.7 配合 -a 或 -l 查看详细信息
pgrep默认只输出PID,这有时候不够直观。用 -a(部分版本是 -l)可以同时显示PID和完整命令行:
bash复制$ pgrep -a nginx
32045 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
32046 nginx: worker process
32047 nginx: worker process
用 -a 显示完整命令行,用 -l(在procps-ng版本里表示显示进程名而非完整命令行)会有细微差别,不同发行版的行为不尽相同。在我的实践中,Debian/Ubuntu上的procps-ng里 -a 和 -l 的行为基本一致,都显示命令行;但在一些老版本或者BusyBox环境里,-l 只显示进程名。如果你写的脚本要在多台机器上跑,建议先在同一发行版上测试确认。为了避免混淆,我用 -a 的时候更多,因为它直观,适合人类阅读;让脚本解析时我反而倾向于默认输出,只取PID。
要列出完整进程信息,可以用 ps -p $(pgrep nginx),把pgrep结果作为ps的过滤条件。这种组合很常用。
3. pgrep的执行过程:它怎么做到的
很多人用pgrep,但没有深究过它内部到底怎么工作。理解它的机制有助于在排查问题时更快定位问题。
pgrep的核心是遍历 /proc 伪文件系统。在Linux里,/proc 目录下每个数字命名的目录都对应一个正在运行的进程,目录名就是PID。pgrep通过读取每个 /proc/PID 下的 comm 文件拿到进程名,读取 cmdline 文件拿到完整命令行,再读取 status 或 stat 文件拿到UID、PPID等信息,然后用正则表达式进行匹配,最后输出符合条件的PID。
这个机制解释了pgrep的几个特性。第一,它比 ps aux | grep 快,因为ps要快照所有进程的全部信息再做格式化输出,pgrep只读取匹配需要的字段,数据量小得多。在进程数量动辄上千的生产服务器上,这个性能差异是能感知到的。第二,pgrep不依赖排序和格式化,直接走内核的进程链表,天然就是当前真实状态的反映。第三,由于它直接读取 /proc,在权限受限的容器里,它只能看到自己PID namespace内可见的进程,这在容器环境下是一种合理的隔离行为。
还有一点值得注意:/proc/PID/cmdline 里的各个参数是用空字符(\0)分隔的,而不是空格。pgrep在内部处理时会做转换,所以你用 -f 匹配时写空格是没问题的。但如果你用 pgrep -f 去匹配一个带正则特殊字符的路径(比如路径里有 [ 或 .),就需要注意转义问题,否则可能匹配不到结果或者匹配到错误的结果。这个细节实操中非常容易踩坑,后面我会详细展开。
4. 核心参数速查与选型逻辑
我把pgrep的常用参数整理成了一张速查表,方便后续查阅。
| 参数 | 作用 | 典型场景 |
|---|---|---|
-x |
精确匹配进程名 | 判断sshd、nginx等固定服务是否在运行 |
-f |
匹配完整命令行 | 区分多个同解释器的脚本进程 |
-u |
按有效用户筛选 | 只查指定用户的进程 |
-U |
按真实用户筛选 | 需要区分真实UID和有效UID的场景 |
-P |
按父进程PID筛选 | 查某个父进程下的所有子进程 |
-n |
返回最新创建的进程 | reload后查找新起的worker |
-o |
返回最旧创建的进程 | 查找master进程 |
-a |
显示PID和完整命令行 | 人工排查时便于阅读 |
-d |
自定义PID分隔符 | 默认换行,可根据需求改成逗号、空格等 |
-L |
同时显示进程名和PID | 在部分版本中等同于 -l 的增强行为 |
-v |
反向匹配 | 取反,显示不匹配条件的进程 |
-c |
只输出计数 | 快速统计进程数量 |
-i |
忽略大小写 | 匹配不区分进程名大小写 |
-G |
按进程组ID筛选 | 排查进程组内所有进程 |
参数选型的一个核心逻辑是:先想清楚你要“精确到进程”还是“精确到命令行”。如果目标是固定服务,用 -x 最稳;如果目标是某个脚本或带参数的进程,用 -f;如果目标是一个用户下的某类进程,用 -u 加 -x 或 -f 组合。我见过不少人在脚本里不加思考地使用 pgrep 进程名,结果因为匹配过宽或者过窄出了问题,所以强烈建议每次都问一句:我到底要精确匹配什么字段?
5. 实战场景:脚本中的存活判断与批量操作
5.1 判断服务是否存活
这是pgrep最常见的脚本应用。传统写法是:
bash复制if ps aux | grep -v grep | grep nginx > /dev/null; then
echo "nginx is running"
fi
有了pgrep,代码会简洁很多:
bash复制if pgrep -x nginx > /dev/null; then
echo "nginx is running"
fi
这里建议用 -x nginx 而不是 nginx,因为Nginx的master进程名是 nginx,worker进程也是 nginx,不会误伤。但如果系统里有别的进程名带nginx子串(比如 nginx_exporter),不加 -x 就会出问题。
判断不存活时,利用exit code:
bash复制if ! pgrep -x nginx > /dev/null; then
systemctl start nginx
fi
pgrep在没有匹配到任何进程时,exit code是1,所以可以直接用 if pgrep ... 做条件判断,不用专门处理返回值。
如果你在脚本里需要多次判断,建议把PID列表存起来重复使用,避免反复遍历procfs:
bash复制pid_list=$(pgrep -x nginx)
if [ -n "$pid_list" ]; then
echo "nginx PIDs: $pid_list"
fi
5.2 批量发送信号
pgrep配合kill可以完成很多批量操作,这是比 pkill 更精细的操作方式。比如优雅停止所有PHP-FPM worker(发送SIGTERM):
bash复制kill -TERM $(pgrep php-fpm)
注意:如果pgrep没有匹配到结果,kill 会因为参数为空而报错。所以脚本里更稳妥的写法是先判断再执行:
bash复制pids=$(pgrep php-fpm)
if [ -n "$pids" ]; then
kill -TERM $pids
fi
也可以用 -d 参数自定义分隔符,把多个PID拼成一个字符串:
bash复制pgrep php-fpm | xargs -r kill -TERM
这里 xargs -r 的作用是:输入为空时不执行kill命令,避免报错。
5.3 统计进程数量
用 -c 参数直接输出匹配数量,在监控脚本里很好用:
bash复制$ pgrep -c httpd
24
如果你想实现类似“httpd进程数少于10个就报警”的监控逻辑:
bash复制count=$(pgrep -c httpd)
if [ "$count" -lt 10 ]; then
echo "WARNING: httpd count is $count"
fi
5.4 反向匹配排除特定进程
-v 参数可以反向匹配,显示所有不满足条件的进程ID。比如你想列出除了root用户之外的所有sshd相关进程:
bash复制$ pgrep -v -U root sshd
不过说实话,-v 的实际应用场景比较有限,因为反向匹配后得到的结果集语义不是特别直观,大多数情况下用其他条件组合能表达得更清楚。
6. 常见问题与排查技巧实录
6.1 pgrep匹配到了自己
这是一个极其经典的问题。当命令中出现pgrep字符时,-f 模式下的正则匹配会命中最前面那个解析命令行参数的bash进程以及pgrep自身。
举例:
bash复制$ pgrep -f test
假设系统里有一个 /opt/scripts/test.sh 在跑,你以为结果只会返回test.sh的PID,但实际可能返回:
bash复制1234 # test.sh
5678 # bash -c pgrep -f test(当前shell进程)
为什么?因为 -f 匹配完整命令行,当前shell执行的命令行是 bash -c pgrep -f test,它包含了“test”这个子串,所以被匹配进来了。同理,pgrep进程自身的命令行 pgrep -f test 也包含“test”,也会命中自己。
规避方法有几种:
- 用
-x精确匹配进程名而不是-f正则匹配完整命令行; - 匹配模式写得足够精确,例如
pgrep -f "test\.sh"而不是pgrep -f test; - 配合
-v排除自身PID,但这样逻辑绕; - 在脚本里把当前shell的PID排除掉。
最推荐的是第一种,写模式的时候尽量用精确模式,不要用太宽泛的子串。这个坑我早期写脚本时踩过很多次,每次排查半天结果发现是pgrep把自己匹配进去了。
6.2 正则表达式特殊字符的匹配陷阱
由于pgrep默认走正则匹配,所以匹配模式里的 .、[、*、+ 等字符都有特殊含义。
举个实际例子:我有个Python脚本路径是 /opt/app/metrics_collector.py,在不转义的情况下运行:
bash复制$ pgrep -f "metrics_collector.py"
这个模式里的 . 是正则“任意一个字符”的通配符,所以它不仅匹配真正的点号,还会匹配 metrics_collectorXpy 这种字符串。虽然实践中很少遇到恰好同名的进程,但严谨的写法应该是转义:
bash复制$ pgrep -f "metrics_collector\.py"
再比如你有一个进程的命令行里包含方括号 [worker:1],如果不转义,正则解析时方括号就变成了字符集定义,行为会非常诡异。遇到这种情况,建议先用 pgrep -a 看看实际命令行,再决定怎么转义。
在脚本中可以使用引号避免shell二次解析,但引号不会消除正则语义,需要明确这一点。想摆脱正则完全按字面匹配,pgrep没有直接的“字面匹配”开关,所以只能靠转义。如果匹配模式特别复杂,也可以考虑退回到ps加grep的方案,虽然性能差一点,但逻辑更直接。
6.3 僵尸进程和未知进程状态
pgrep默认会匹配所有状态的进程,包括僵尸进程(Zombie)。僵尸进程本身已经结束了,但仍占据一个PID条目,直到父进程调用wait回收它。
如果你的监控脚本用 pgrep -c nginx 统计数量,发现数量偏多,排查时可以用 ps -o pid,stat,cmd -p <PID> 看状态。如果看到STAT列是 Z,那就是僵尸进程。
处理僵尸进程的办法不是结束它(它已经结束了),而是结束或者通知它的父进程去回收。pgrep在这里的作用是帮你快速定位僵尸进程的PID和它的父进程:
bash复制$ pgrep -x nginx | while read pid; do
stat=$(ps -o stat= -p $pid)
echo "$pid $stat"
done
如果看到僵尸进程频繁出现,重点查一下它的父进程是否工作不正常,比如Nginx master进程卡死、PHP-FPM主进程异常等。
6.4 UID和有效UID的区别
-u 和 -U 的区别,在很多系统管理员那里也是一笔糊涂账。
-u按有效用户ID(effective UID)匹配-U按真实用户ID(real UID)匹配
对于大多数普通进程,真实UID和有效UID是一样的。但存在setuid程序,比如 passwd 命令,它的真实UID是当前用户,有效UID是root。这种情况下,pgrep -u root passwd 能匹配到,pgrep -U root passwd 匹配不到。
在排查安全问题时,你可以通过区分有效UID和真实UID来判断某个进程是否以提升后的权限运行。这也算pgrep在安全排查场景下的一个小用途。
6.5 大进程量下的性能表现
在几千个进程的服务器上,pgrep 的性能优势很明显。做一个简单对比:
bash复制$ time pgrep nginx
$ time ps aux | grep nginx | grep -v grep
实测下来(个人经验数据,不同机器会有差异),pgrep的耗时大约是ps管道方案的十分之一甚至更低。原因前面说过了:pgrep只读procfs里必要的字段,而ps要完整读取所有进程的全部状态并做格式化输出。如果你在监控脚本里每条几秒就要跑一次进程查询,用pgrep能明显降低系统负载。
但也要注意,-f 匹配完整命令行需要读取每个进程的cmdline文件,性能比默认匹配进程名略慢。如果进程数量很大且你对性能有要求,优先用不加 -f 的匹配方式。
7. pgrep与其他进程管理命令的搭配
7.1 pgrep + ps 组合
pgrep负责“筛PID”,ps负责“看详情”,两者天然互补。我经常用的组合:
bash复制$ ps -fp $(pgrep -x nginx)
-fp 里的 -f 是ps参数,显示完整命令行,-p 指定PID。这条命令一行就能看到所有Nginx进程的完整信息。
如果你用的ps版本支持 --forest,还可以显示进程树:
bash复制$ ps -fp --forest $(pgrep -x nginx)
这个输出能直观展示Nginx master和worker的层级关系,排查进程资源问题时非常方便。
7.2 pgrep + top / htop
在交互式界面里,pgrep可以帮助你快速聚焦到特定进程。比如htop里按F4过滤进程名,但如果你想直接跳到某个嫌疑进程,可以先用pgrep拿到PID,然后htop里按F3搜索该PID。这个操作流在定位高CPU进程时很高效。
7.3 pgrep + pkill 的相爱相杀
pkill和pgrep是同一套匹配逻辑,区别在于pkill向匹配到的进程发送信号,默认是SIGTERM。我用pgrep先预演,用pkill执行,是一个很稳妥的工作流:
bash复制# 预演:看看会匹配到什么
$ pgrep -a -f "celery worker"
# 确认无误后执行
$ pkill -f "celery worker"
这里强烈建议在生产环境养成“先pgrep预演,再pkill执行”的习惯。因为pkill的匹配逻辑和pgrep完全一样,正则问题、匹配过宽问题在pkill下会造成事故,但在pgrep下只是“看到一堆PID”。多花两秒预演,能避免很多灾难。
我的一个经验是:pkill永远加 -x 或者足够完整的 -f 模式,并且先用pgrep确认匹配范围。
7.4 pgrep + systemd 的协作
在使用systemd的现代发行版里,很多人喜欢用 systemctl status 查进程,但systemctl有时候因为单元状态显示不准确,还要配合pgrep做二次确认。比如:
bash复制$ systemctl status nginx
$ pgrep -a -x nginx
一个是服务管理器视角,一个是内核进程表视角,两者交叉验证,能发现一些诡异的问题,比如服务处于active状态但进程已经消失,或者有多个nginx实例在跑但systemd只管理了其中一个。这种不一致的排查,pgrep是很趁手的工具。
8. 实操心得与避坑指南
最后分享几个我自己积累的经验,都是实际操作中一步一步踩出来的。
第一,脚本里用pgrep的时候,尽量把匹配模式写“窄”。匹配模式越宽,误伤概率越大。我见过有人写 pgrep -f python 想匹配某个Python脚本,结果把一个跑着生产训练任务的python进程也捞出来了,批量kill的时候差点酿成事故。花时间把匹配模式写精确,是对生产环境的基本尊重。
第二,写进crontab和systemd timer里的pgrep命令,一定要测试exit code的语义。pgrep在正常执行但无匹配时返回1,有匹配时返回0。这个行为和grep一致,但如果你在脚本里用了 set -e,就要格外小心——pgrep 无匹配返回1可能导致整个脚本提前退出。正确做法是在需要容忍无匹配结果的地方显式处理exit code:
bash复制set -e
if pgrep -x nginx; then
echo "running"
else
echo "not running"
fi
第三,容器环境下pgrep的可视范围受PID namespace限制。如果你在Docker容器里跑 pgrep nginx,它只能看到容器内的进程,看不到宿主机上的nginx。理解这一点在排查“容器里看不到进程”的问题时能省很多时间。反过来,如果你想在宿主机上查某个容器内的进程,用 pgrep 是做不到的,得结合cgroup过滤,或者用 docker top。
第四,pgrep的输出顺序在不同系统上可能不一致。默认情况下它按PID升序输出,但如果你依赖这个顺序做逻辑处理,建议显式排序:
bash复制$ pgrep -x nginx | sort -n
尤其是在写需要稳定输出顺序的脚本时,不要想当然地认为所有机器上的输出顺序都一样。
第五,匹配模式中的中文或其他非ASCII字符,在部分终端环境和locale设置下可能匹配异常。这个问题虽然不是pgrep本身的问题,但如果你管理的系统里有中文进程名或中文路径,遇到匹配不上时先检查一下locale设置。
就我个人经验,pgrep是把“查询进程”这件小事做到极致的典范。它不花哨,不复杂,但每一个参数设计都对应着实际运维中的真实需求。多用几次、踩过几个坑之后,你会慢慢形成一套适合自己的查询习惯,这套习惯在排查故障时会成为你的直觉。如果你还没有把pgrep变成自己工具箱里的常备命令,建议从今天开始,把 ps aux | grep 的习惯逐步替换成 pgrep -a,用上一段时间,你会回来感谢这次的改变。
