pkill 命令详解与参数实测(Ubuntu 22.04)
pkill 把查找与发信号合成一条命令:本文在 Ubuntu 22.04(procps-ng 3.3.17)逐参数实测,覆盖默认 TERM 实证、--signal/-9 等价写法、-u 按用户、-n/-o 只杀最新最旧、-e 回显所杀、-c 计数陷阱、-t 踢终端会话、-x 精确名与部分模式差异、-USR1 自定义信号送达,重点剖析 pkill bash 自杀风险与唯一标记安全替代方案。
pkill 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 pkill 的每一个常用参数——按进程名与
-f全命令行两种匹配方式、默认 TERM 与--signal/-9显式信号、-u按用户、-n只杀最新、-t踢终端会话、-x精确整名、-USR1自定义信号送达、部分模式与精确名的差异、无匹配 rc=1,以及"pkill bash 自杀风险"的安全替代方案,全部命令与回显已在 Ubuntu 22.04(procps-ng 3.3.17)实测通过。
一、环境信息
本节记录实测环境与演示数据,便于读者对照复现。pkill 与 pgrep 是同一软件包的孪生工具:匹配逻辑完全相同,差别只在动作——pgrep 打印 PID,pkill 直接向所有命中进程发信号。它把"查找 + 逐个 kill"两步合成一条命令,是批量管理进程的主力工具。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- pkill 版本:procps-ng 3.3.17(见 2.3 实测)
- Shell:GNU bash 5.1.16(1)-release,root 身份执行
- 演示目录:
/root/demo/pkill/ - 测试用户:普通用户
tester(uid=1012,用于-u按用户实验) - 演示进程:3 个改名副本
myworker(cp /usr/bin/sleep /root/demo/pkill/myworker)、root 的sleep 300×3 与sleep 120×1、tester 用户的sleep 77/sleep 88、两个 trap 工作脚本与一个改名 bashmybash
二、命令概览
2.1 语法与测试数据准备
pkill 的语法是 pkill [options] <pattern>:按扩展正则(ERE)匹配进程并向每个命中进程发送信号,不指定信号时默认发 SIGTERM。匹配规则与 pgrep 一致:默认只匹配进程名(comm,最长 15 字符),-f 匹配全命令行;无匹配时退出码 1。默认情况下 pkill 不会匹配自己,但可能匹配到其他同名进程——这是 3.9 节安全实践的核心议题。
准备演示数据(多组同名、跨用户、带标记的进程,供各参数分别命中):
cp -f /usr/bin/sleep /root/demo/pkill/myworker
/root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 &
sleep 300 & sleep 300 & sleep 300 & sleep 120 &
su tester -c 'sleep 77' & SU1=$!
su tester -c 'sleep 88' & SU2=$!
用 ps 对账确认全部演示进程就位(tester 的两个 sleep 以 su 前台方式持有,被杀时可正常回收):
root@ubuntu2204:~# ps -eo pid,user,comm,args | grep -E '[s]leep (300|120|77|88)|[m]yworker'
334 root myworker /root/demo/pkill/myworker 300
335 root myworker /root/demo/pkill/myworker 300
336 root myworker /root/demo/pkill/myworker 300
337 root sleep sleep 300
338 root sleep sleep 300
339 root sleep sleep 300
340 root sleep sleep 120
341 root su su tester -c sleep 77
342 root su su tester -c sleep 88
344 tester sleep sleep 88
345 tester sleep sleep 77
2.2 --help 全量输出(逐行照抄)
root@ubuntu2204:~# pkill --help
Usage:
pkill [options] <pattern>
Options:
-<sig>, --signal <sig> signal to send (either number or name)
-q, --queue <value> integer value to be sent with the signal
-e, --echo display what is killed
-c, --count count of matching processes
-f, --full use full process name to match
-g, --pgroup <PGID,...> match listed process group IDs
-G, --group <GID,...> match real group IDs
-i, --ignore-case match case insensitively
-n, --newest select most recently started
-o, --oldest select least recently started
-O, --older <seconds> select where older than seconds
-P, --parent <PPID,...> match only child processes of the given parent
-s, --session <SID,...> match session IDs
-t, --terminal <tty,...> match by controlling terminal
-u, --euid <ID,...> match by effective IDs
-U, --uid <ID,...> match by real IDs
-x, --exact match exactly with the command name
-F, --pidfile <file> read PIDs from file
-L, --logpidfile fail if PID file is not locked
-r, --runstates <state> match runstates [D,S,Z,...]
--ns <PID> match the processes that belong to the same
namespace as <pid>
--nslist <ns,...> list which namespaces will be considered for
the --ns option.
Available namespaces: ipc, mnt, net, pid, user, uts
-h, --help display this help and exit
-V, --version output version information and exit
For more details see pgrep(1).
与 pgrep --help 对比:过滤选项几乎一一对应,头部多出 --signal/-q(信号指定与 sigqueue 队列值),少了输出类选项、多了 -e(回显所杀目标)。帮助末行 "see pgrep(1)" 也点明二者共用同一手册页。
2.3 版本信息
root@ubuntu2204:~# pkill --version | head -2
pkill from procps-ng 3.3.17
三、参数逐项实测
3.1 按进程名杀一组:-x 与默认匹配
一条命令终止全部同名进程——演示数据里的 3 个 myworker 被 pkill -x myworker 一次清空,验证计数归零:
root@ubuntu2204:~# pkill -x myworker; echo rc=$?
rc=0
root@ubuntu2204:~# sleep 0.3; pgrep -c -x myworker || echo all-myworker-dead
0
all-myworker-dead
💡 要点:这就是 pkill 与 kill 的本质区别——kill 只认 PID(一次一个或一组显式 PID),pkill 按模式匹配后逐个投递信号,天然适合"杀一批"。
3.2 默认信号 TERM 实证(trap worker)
pkill 不带信号参数时默认发什么?用 trap 工作脚本实证:worker 注册 TERM 处理器,pkill -f 模式 命中后,worker 打印捕获信息并优雅退出:
cat > /root/demo/pkill/trap-pkill.sh <<'EOF'
#!/bin/bash
trap 'echo "caught TERM from pkill, do cleanup, exit 0"; exit 0' TERM
echo "pkill-trap-worker pid=$$"
sleep 300 &
while :; do wait; done
EOF
root@ubuntu2204:~# bash /root/demo/pkill/trap-pkill.sh --pkill-trap-marker & WP=$!; sleep 0.5; pkill -f pkill-trap-marker; wait $WP 2>/dev/null; echo worker_rc=$?
pkill-trap-worker pid=352
caught TERM from pkill, do cleanup, exit 0
worker_rc=0
worker_rc=0:进程捕获 TERM、执行清理、主动退出。结论:pkill 默认信号是 SIGTERM,与 kill 默认行为一致。
3.3 -u 按用户杀
-u 按有效 UID 过滤目标——只杀 tester 用户的 sleep(344/345),root 的同名进程不受影响。注意输出中两行 Terminated:那是 su 前台会话检测到其子进程被终止后打印的会话消息,属正常回显:
root@ubuntu2204:~# pkill -u tester -x sleep; echo rc=$?
Terminated
Terminated
rc=0
root@ubuntu2204:~# kill $SU1 $SU2 2>/dev/null; sleep 1; pgrep -c -u tester -f 'sleep (77|88)' || echo tester-clean
tester-clean
清理后按 -f 计数验证 tester 名下已无目标进程(-f 匹配 cmdline,不会把僵尸误计入)。
3.4 -9 强杀与 --signal 等价写法
-9 与 --signal KILL 是两种等价的信号指定写法(帮助首行 -<sig>, --signal <sig> signal to send (either number or name))。锚定模式精确命中 root 的 sleep 300 ×3:
root@ubuntu2204:~# pkill -9 -f '^sleep 300$'; echo rc=$?
bash: line 3: 337 Killed sleep 300
bash: line 3: 338 Killed sleep 300
bash: line 3: 339 Killed sleep 300
rc=0
(三行 Killed 是外层 Shell 对被杀后台作业的终止通知,正是三个 sleep 300 阵亡的现场记录。)再验证长选项写法杀掉 sleep 120,最后确认全清:
root@ubuntu2204:~# pkill --signal KILL -f '^sleep 120$'; echo rc=$?
bash: line 3: 340 Killed sleep 120
rc=0
root@ubuntu2204:~# sleep 0.3; pgrep -c -f '^sleep (300|120)$' || echo all-root-sleep-dead
0
all-root-sleep-dead
3.5 -n 只杀最新 / -o 只杀最旧
当同名进程有多个实例时,-n 把打击面收窄到最新启动的那一个。重起 3 个 myworker(364/365/366),pkill -n -x myworker 后只少了最新的 366:
root@ubuntu2204:~# pkill -x myworker 2>/dev/null; /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & sleep 0.3; ps -eo pid,comm,args | grep '[m]yworker'
364 myworker /root/demo/pkill/myworker 300
365 myworker /root/demo/pkill/myworker 300
366 myworker /root/demo/pkill/myworker 300
root@ubuntu2204:~# pkill -n -x myworker; echo rc=$?
rc=0
root@ubuntu2204:~# sleep 0.3; ps -eo pid,comm,args | grep '[m]yworker'
364 myworker /root/demo/pkill/myworker 300
365 myworker /root/demo/pkill/myworker 300
剩下 364/365 完好,再补一刀清空收尾:
root@ubuntu2204:~# pkill -x myworker; sleep 0.2; echo rest-killed
rest-killed
对称的 -o 命中最旧启动的那一个:起两个实例(58/59),pkill -o 后只剩最新的 59:
root@ubuntu2204:~# cp -f /usr/bin/sleep /root/demo/pkill/myworker; /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & sleep 0.3; ps -eo pid,comm,args | grep '[m]yworker'
58 myworker /root/demo/pkill/myworker 300
59 myworker /root/demo/pkill/myworker 300
root@ubuntu2204:~# pkill -o -x myworker; echo oldest_rc=$?
oldest_rc=0
root@ubuntu2204:~# ps -eo pid,comm,args | grep '[m]yworker'
59 myworker /root/demo/pkill/myworker 300
root@ubuntu2204:~# pkill -x myworker; sleep 0.2; echo cleaned
cleaned
💡 要点:
-n的典型场景是"只重启最新失控实例"(老实例还在服务流量);-o适合"淘汰最老的陈旧实例"。二者都只投递给一个进程。
3.6 -e 回显所杀目标
pkill 默认静默——rc=0 只说明"投了信号",但杀了谁看不到。-e(echo)逐个打印被击杀目标,批量操作时是最好的事后审计记录:
root@ubuntu2204:~# cp -f /usr/bin/sleep /root/demo/pkill/myworker; /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & sleep 0.3; echo started
started
root@ubuntu2204:~# pkill -e -x myworker; echo echo_rc=$?
myworker killed (pid 35)
myworker killed (pid 36)
myworker killed (pid 37)
echo_rc=0
root@ubuntu2204:~# pgrep -c -x myworker || echo all-dead
0
all-dead
3.7 -c 计数(注意:照样发信号,不是预演)
pkill -c 把输出换成命中数量——但它仍然照常投递信号,只是把"逐个报 PID"(-e)换成"报总数"。实测:pkill -c -x myworker 输出 2,紧接着 pgrep -c 已归零:
root@ubuntu2204:~# /root/demo/pkill/myworker 300 & /root/demo/pkill/myworker 300 & sleep 0.3; echo two-started
two-started
root@ubuntu2204:~# pkill -c -x myworker; echo count_rc=$?
2
count_rc=0
root@ubuntu2204:~# pgrep -c -x myworker; echo alive_check_rc=$?
0
alive_check_rc=1
⚠️ 注意:想要只统计、不动手的安全预演,请用
pgrep -c 模式(pgrep 本就不发信号);pkill -c的语义是"统计一下我杀了几个"。
3.8 -t 踢终端会话
-t 按控制终端过滤目标。用 script 构造一个带伪终端的会话(内部跑 sleep 300),确认会话挂在 pts/0 后,一条 pkill -t pts/0 把会话内进程全部踢掉:
root@ubuntu2204:~# script -qfc 'sleep 300' /dev/null & SCD=$!; sleep 0.7; CHILD=$(pgrep -P $SCD | head -1); TTY=$(ps -o tty= -p $CHILD | tr -d ' '); echo session_pid=$SCD child=$CHILD tty=$TTY
session_pid=392 child=394 tty=pts/0
root@ubuntu2204:~# pkill -t $TTY; echo rc=$?
rc=0
root@ubuntu2204:~# sleep 0.5; pgrep -t $TTY -l || echo pty-session-killed
pty-session-killed
root@ubuntu2204:~# pkill -f 'script -qfc' 2>/dev/null; echo script-cleaned
script-cleaned
pgrep -t 已查不到该终端上的进程,会话踢除成功。这正是管理员处理"僵死登录会话"的标准手法:先 who/w 看 pts 编号,再 pkill -t pts/N。
3.9 -x 精确名 vs 部分模式匹配
pkill 的匹配语义与 pgrep 完全一致:默认正则部分匹配,-x 整名等值。短模式 mywor 在 -x 下无匹配(rc=1,目标毫发无损),去掉 -x 立即命中并击杀:
root@ubuntu2204:~# /root/demo/pkill/myworker 300 & sleep 0.3; pkill -x mywor; echo exact_short_rc=$?
exact_short_rc=1
root@ubuntu2204:~# pgrep -x myworker >/dev/null && echo still-alive
still-alive
root@ubuntu2204:~# pkill mywor; echo partial_rc=$?
partial_rc=0
root@ubuntu2204:~# sleep 0.3; pgrep -x myworker || echo killed-by-partial-pattern
killed-by-partial-pattern
⚠️ 对比 killall:
killall wor会报 "no process found"(它按精确名匹配,详见《killall 命令详解与参数实测(Ubuntu 22.04)》),而pkill wor部分匹配直接杀——pkill 更灵活也更容易误伤,killall 更严格更安全。
3.10 -USR1 自定义信号送达验证
信号不止"杀"一种用途。worker 注册 USR1 处理器(模拟"滚动日志"),pkill -USR1 -f 标记 定向投递,再发默认 TERM 让它退出:
cat > /root/demo/pkill/usr1-worker.sh <<'EOF'
#!/bin/bash
trap 'echo "USR1 received at $(date +%T), rolling log"' USR1
trap 'echo "TERM received, exiting"; exit 0' TERM
echo "usr1-worker pid=$$"
sleep 300 &
while :; do wait; done
EOF
root@ubuntu2204:~# bash /root/demo/pkill/usr1-worker.sh --usr1-marker & UP=$!; sleep 0.5; pkill -USR1 -f usr1-marker; echo usr1_send_rc=$?; sleep 0.5; pkill -f usr1-marker; wait $UP 2>/dev/null; echo worker_rc=$?
usr1-worker pid=384
usr1_send_rc=0
USR1 received at 10:29:27, rolling log
TERM received, exiting
worker_rc=0
时间戳(10:29:27)证明 USR1 确实在 pkill -USR1 执行后送达 worker,最后 TERM 让其优雅退出 rc=0。结论:pkill -USR1 等任意信号均可投递,配合唯一标记可实现"向指定进程群广播控制信号"。
3.11 安全实践:用唯一标记模式替代"pkill bash"式裸杀
为什么不能演示 pkill bash? 因为它按部分模式匹配进程名 bash,会命中当前 Shell、外层脚本、以及系统里所有 bash 进程——实测脚本一执行就会把自己的执行环境杀掉,终端直接断线。这条命令本身就是运维事故的经典来源。
安全替代方案:给目标进程打唯一标记,用 -f 标记 精确打击。把 bash 复制为改名进程 mybash,启动时携带标记参数 --demo-tag-42x:
root@ubuntu2204:~# cp -f /bin/bash /root/demo/pkill/mybash; /root/demo/pkill/mybash -c 'while :; do sleep 300; done' --demo-tag-42x & MB=$!; sleep 0.5; ps -eo pid,comm,args | grep '[m]ybash'
407 mybash /root/demo/pkill/mybash -c while :; do sleep 300; done --demo-tag-42x
root@ubuntu2204:~# pkill -f 'demo-tag-42x'; echo rc=$?
rc=0
root@ubuntu2204:~# sleep 0.3; pgrep -f demo-tag-42x || echo tagged-bash-gone
tagged-bash-gone
目标 bash 进程(comm 是 mybash、命令行含唯一标记 42x)被精确清除,当前 Shell 安然无恙——这就是可安全实操的等价演示。
3.12 无匹配 rc=1
与 pgrep 一致:无匹配时静默退出、退出码 1,方便脚本用 &&/|| 消费:
root@ubuntu2204:~# pkill -f zzz-no-such-marker-999; echo rc=$?
rc=1
四、实战示例
4.1 批量清理测试残留
压测/联调结束后清理自己拉起的整组进程。先用锚定模式发 TERM(优雅),再对残存者升级 KILL——与 kill 篇"先 TERM 后 KILL"的兜底顺序完全一致:
root@ubuntu2204:~# pkill -9 -f '^sleep 300$'; echo rc=$?
bash: line 3: 337 Killed sleep 300
bash: line 3: 338 Killed sleep 300
bash: line 3: 339 Killed sleep 300
rc=0
root@ubuntu2204:~# pkill --signal KILL -f '^sleep 120$'; echo rc=$?
bash: line 3: 340 Killed sleep 120
rc=0
root@ubuntu2204:~# sleep 0.3; pgrep -c -f '^sleep (300|120)$' || echo all-root-sleep-dead
0
all-root-sleep-dead
💡 要点:先
pgrep -f 模式预演确认命中面,再原样换成 pkill 执行——两条命令匹配引擎相同,预演结果就是打击清单。
4.2 踢掉指定终端的僵死会话
处理僵死登录会话:定位 pts 编号后整段踢除(3.6 已实测完整链路,此处给场景化闭环):
root@ubuntu2204:~# script -qfc 'sleep 300' /dev/null & SCD=$!; sleep 0.7; CHILD=$(pgrep -P $SCD | head -1); TTY=$(ps -o tty= -p $CHILD | tr -d ' '); echo session_pid=$SCD child=$CHILD tty=$TTY
session_pid=392 child=394 tty=pts/0
root@ubuntu2204:~# pkill -t $TTY; echo rc=$?
rc=0
root@ubuntu2204:~# sleep 0.5; pgrep -t $TTY -l || echo pty-session-killed
pty-session-killed
4.3 自定义信号做"运行时控制"
信号不止"杀"一种用途——USR1/USR2 常被长驻进程用作"滚动日志""重开连接"等运行时控制指令。完整闭环(3.10 已实测核心命令):worker 注册 USR1 处理器 → pkill -USR1 -f usr1-marker 定向投递 → worker 打印滚动记录 → pkill -f usr1-marker 发 TERM 优雅收尾:
root@ubuntu2204:~# bash /root/demo/pkill/usr1-worker.sh --usr1-marker & UP=$!; sleep 0.5; pkill -USR1 -f usr1-marker; echo usr1_send_rc=$?; sleep 0.5; pkill -f usr1-marker; wait $UP 2>/dev/null; echo worker_rc=$?
usr1-worker pid=384
usr1_send_rc=0
USR1 received at 10:29:27, rolling log
TERM received, exiting
worker_rc=0
相比 kill 需要先 pgrep 拿 PID,pkill 一条命令完成"找 + 发",对多实例进程群的批量控制优势明显;精确性靠唯一标记模式(-f 唯一标记)保证,这也是 3.11 安全实践的自然延伸。
五、注意事项与常见问题
先给出本组四件工具的选型速查,再进入逐条 Q&A——
💡 技术拓展:kill / pgrep / pkill / killall 选型对照——
维度 kill pgrep pkill killall 定位 按 PID/作业号发信号 按模式查 PID(不动手) 按模式发信号 按精确进程名发信号 匹配方式 不匹配,直接给 PID ERE 部分匹配(comm 或 -f全命令行)同 pgrep 精确整名( -r才用正则)一次目标数 显式列出的若干 PID 输出全部命中 全部命中 全部同名 作业号 %1 支持(bash 内置) 不支持 不支持 不支持 默认信号 SIGTERM 不发信号 SIGTERM SIGTERM 典型场景 已知 PID 的精确操作 预演/探测/组合命令 批量清理、模式化运维 按名停服务,防误伤要求高时 组合建议:不确定命中面时先 pgrep 预演 → 再 pkill/kill 落地;对"按名杀、宁可杀不中不可杀错"的场合选 killall。
Q1:为什么"pkill bash"是高危命令?
A:默认部分匹配下它会命中所有 bash 进程——包括你自己正在用的这个 Shell。虽然 procps 会排除 pkill 自身,但排除不了同名的外层 Shell/SSH 会话,实测脚本里跑它会当场断线。安全做法:给目标进程携带唯一标记参数,用 pkill -f 唯一标记 精确打击(3.9 实测演示)。
Q2:pkill 和 killall 有什么区别?
A:pkill 按正则模式匹配(可部分命中、可用 -f 匹配全命令行);killall 按精确进程名匹配(killall wor 直接报无此进程)。pkill 灵活但需防误伤,killall 严格但只认完整名。二者默认信号都是 TERM。
Q3:-f 匹配全命令行有什么风险?
A:与 pgrep 相同——包装进程(su tester -c sleep 77、bash run.sh)的命令行里带着目标字样会被一并命中。发信号前先用同模式的 pgrep 预演(两者匹配引擎一致),或加 ^...$ 锚定收窄。
Q4:输出里突然出现的 "Terminated"、"Killed" 是 pkill 打印的吗?
A:不是。pkill 本身静默(除非 -e 回显)。这些行来自外层 Shell 对被杀后台作业的终止通知,或 su 等会话管理进程检测到子进程被信号终止时打印的会话消息(3.3/3.4 实测现场)。
Q5:pkill 后 rc=0 但进程还活着?
A:rc=0 只表示"信号已投递",不代表进程会退出——TERM 可被捕获或忽略(kill 篇 3.6/3.8 有完整实证)。先稍等再用 pgrep -c 复查,仍活着再升级 pkill -9。
Q5a:pkill -c 能当"预演"用吗?
A:不能。实测证明 pkill -c 在报数的同时照常投递信号(3.7 中计数 2 之后进程全部阵亡)。安全预演请用 pgrep -c(pgrep 从不发信号),或 pkill --signal 0 模式 做"零信号"探测式演练。
Q6:如何只杀某个用户的进程?
A:pkill -u 用户名 模式(3.3 实测)。pkill -u 用户名 不带模式时会终止该用户名下当前用户有权杀的全部进程,等同于把对方登出,务必慎用。
六、参考链接
- pgrep(1)/pkill(1) 手册(man7.org,二者共用):https://man7.org/linux/man-pages/man1/pgrep.1.html
- signal(7) 信号完整列表与语义:https://man7.org/linux/man-pages/man7/signal.7.html
- procps-ng 项目主页:https://gitlab.com/procps-ng/procps
- Ubuntu 22.04 procps 软件包页:https://packages.ubuntu.com/jammy/procps
- 姊妹篇:《kill 命令详解与参数实测(Ubuntu 22.04)》《pgrep 命令详解与参数实测(Ubuntu 22.04)》《killall 命令详解与参数实测(Ubuntu 22.04)》