witr v0.3.4 常用 CLI 命令详解
以六个真实排障场景实测 witr v0.3.4:端口占用、不明进程、容器归属、文件锁、批量巡检与 JSON 自动化,附命令速查表与三条排障实录。
witr v0.3.4 常用 CLI 命令详解
目标:以真实服务器上的六个高频排障场景为主线,逐条实测 witr v0.3.4 的命令组合——端口占用排查、不明进程溯源、容器归属确认、文件锁定位、进程树巡检、JSON 自动化集成,并把实测中踩过的坑沉淀为排障实录。全部命令与回显均在 Ubuntu 24.04.5 LTS(x86_64)真实服务器上执行取得;参数语义与旗标全集见《witr v0.3.4 配置参数详解》,安装见《Ubuntu 24.04 安装 witr v0.3.4 指南》。
一、环境信息
本文所有命令在以下环境逐条实测,回显为实际输出:
| 项 | 值 |
|---|---|
| 操作系统 | Ubuntu 24.04.5 LTS(x86_64,systemd 在管,运行 SSH、cron 等常规服务) |
| witr 版本 | v0.3.4(commit 7e46fa68,2026-10-04 构建),安装于 /usr/local/bin/witr |
| 运行用户 | root |
| 演示负载 | python3 -m http.server 8141(临时 HTTP 服务)、一个跑 python:3.12-alpine 的演示负载(进程名 python,监听 8142) |
| 配套工具 | jq(JSON 解析演示用)、systemctl(取单元主 PID) |
💡 说明:文中的 PID、端口与会话状态是实测时点的真实快照。你的服务器上 PID 必然不同,跟着操作时先用
witr <名字>或ss -ltnp找到自己的目标,再套用相同命令组合。
二、命令体系总览
witr 只有一条主命令,所有行为靠旗标组合。按「查什么 × 怎么呈现」画成速查地图:
| 查什么 | 命令骨架 | 典型呈现 |
|---|---|---|
| 进程名 | witr <名字> / witr <名字> -x | 标准报告;多匹配列候选(exit 4) |
| 进程号 | witr --pid <PID> | 标准报告,可加 --verbose 深挖 |
| 端口 | witr --port <端口> | 标准报告 + 套接字归属 |
| 文件 | witr --file <路径> | 锁/日志持有者 |
| 容器 | witr --container <名/ID/镜像> | 镜像、compose、重启策略 |
| 多目标 | 上述任意混用可重复 | 分段输出,退出码取最严重 |
| 只要祖先链 | 追加 --short | 一行因果链 |
| 要树形图 | 追加 --tree | 树 + 最多 10 个子进程 |
| 只要警告 | 追加 --warnings | 体检摘要 |
| 只要环境变量 | 追加 --env | 生效配置逐行列出 |
| 给脚本/AI | 追加 --json | 字段契约输出 |
| 无参数 | witr | 进入 TUI 仪表盘 |
退出码是这套命令体系的「第二输出」:0 干净、1 有警告、2 未找到、3 需提权、4 无效/歧义、5 内部错误、6 无法溯源(详见 C1 篇第七章,本文场景中随用随验证)。
三、场景一:端口被占用排查
问题:部署新服务时报端口被占,「8141 是谁占的、能不能杀」。
第 1 步:确认占用事实(对照传统手段):
ss -ltnp | grep 8141
实测回显:
LISTEN 0 2048 0.0.0.0:8141 0.0.0.0:* users:(("python3",pid=744395,fd=3))
ss 给到 PID 就停了——「这个 python3 为什么在跑、是哪个服务的一部分」还得继续查。
第 2 步:一条命令问到根:
witr --port 8141
实测回显:
Target : python3
Process : python3 (pid 744395)
User : root
Command : python3 -m http.server 8141 --bind 0.0.0.0
Started : just now (Sun 2026-10-25 09:26:12 +08:00)
Why It Exists :
systemd (pid 1) → ? (original parent exited) → python3 (pid 744395)
Source : session-17.scope (systemd)
Description : Session 17 of User root
Unit File : /run/systemd/transient/session-17.scope
Working Dir : /opt/data/quzuo/tmp/witr
Git Repo : quzuo (master)
Sockets : 0.0.0.0:8141 (TCP | LISTENING)
Warnings :
• Process is listening on a public interface
• Process is running as root
• Process sets LD_PRELOAD (potential library injection)
• Process sets DYLD_* variables (potential library injection): DYLD_LIBRARY_PATH
结论一次读完:占用者 pid 744395,命令行 python3 -m http.server 8141,由 root 在会话 scope(session-17.scope)里手工启动(原始 shell 已退出,因果链如实标注 ? (original parent exited)),工作目录还带着 Git 仓库信息。退出码实测 exit=1(有警告)。
第 3 步:一行因果链贴工单:
witr --port 8141 --short
实测回显:
systemd (pid 1) → ? (original parent exited) → python3 (pid 744395)
第 4 步:处置判断。来源是会话 scope 而非 *.service 单元 → 它不是任何业务服务的常驻组件,重启服务器或杀掉 PID 都能释放,且不会自动拉起。若来源显示的是某个 service/容器,处置动作就完全不同(改单元配置或改容器端口映射),这正是溯源的价值所在。
💡 技术拓展:端口由 docker-proxy、systemd socket 激活等「代持」时,
ss -ltnp只能看到代持进程(如docker-proxy),witr 会继续回溯到背后真正的服务或容器(官方 README §8.1 Port→Container fallback),这类场景是 witr 相对ss/lsof的显著增量。
四、场景二:不明进程溯源
问题:巡检发现名字眼熟的 sshd,想知道监听主进程与一堆会话进程的关系,以及该不该有这么多。
第 1 步:按名字查,先看候选:
witr sshd
实测回显:
Multiple matching processes found:
[1] sshd (pid 7194)
sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
[2] sshd (pid 25397)
sshd: root@notty
[3] sshd (pid 88186)
sshd: root@notty
[4] sshd (pid 214186)
sshd: root@pts/2
[5] sshd (pid 214891)
sshd: root@notty
Re-run with:
witr --pid <pid>
多命中不瞎选——列清单、提示复查、退出码 4。命令行描述已能区分角色:[listener] 是监听主进程,root@notty/root@pts/2 是会话子进程。
第 2 步:复查主进程:
witr --pid 7194
实测回显:
Target : sshd
Process : sshd (pid 7194)
User : root
Service : ssh.service
Command : sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
Started : 2 hours ago (Sun 2026-10-25 06:46:32 +08:00)
Why It Exists :
systemd (pid 1) → sshd (pid 7194)
Source : ssh.service (systemd)
Description : OpenBSD Secure Shell server
Unit File : /usr/lib/systemd/system/ssh.service
Working Dir : /
Sockets : 0.0.0.0:22 (TCP | LISTENING)
[::]:22 (TCP6 | LISTENING)
Warnings :
• Process is listening on a public interface
• Process is running as root
• Process is running from a suspicious working directory: /
确认:由 ssh.service(systemd)拉起、监听 22、单元文件位置明确。三条警告属 SSH 服务的正常形态,核对预期即可。
第 3 步:树形看全家:
witr --pid 7194 --tree
实测回显:
systemd (pid 1)
└─ sshd (pid 7194)
├─ sshd (pid 25397)
├─ sshd (pid 88186)
├─ sshd (pid 214186)
└─ sshd (pid 214891)
监听主进程下的 4 个会话子进程一目了然——数量与会话数对得上,无异常多出的进程。
第 4 步:深挖单个会话进程(--verbose 附加 CPU/内存/IO/文件描述符明细):
witr --pid 7194 --verbose
实测回显(节选,报告主体同上,此处仅列追加部分):
CPU (avg) : 0.0%
Memory:
Virtual : 11.8 MB
Resident : 8.2 MB
Shared : 7.1 MB
I/O Statistics:
Read : 3.4 MB (413178 ops)
Write : 72.0 KB (5382 ops)
Open Files : 5 of 1024 (0%)
File Descriptors: 5/1024
0 -> /dev/null
1 -> socket:[51663]
2 -> socket:[51663]
3 -> socket:[23346]
4 -> socket:[26701]
Children of sshd (pid 7194):
├─ sshd (pid 25397)
├─ sshd (pid 88186)
├─ sshd (pid 214186)
└─ sshd (pid 214891)
资源水位正常、fd 无泄漏迹象,溯源闭环。
五、场景三:容器归属确认
问题:宿主机上 containerd-shim 的一堆子进程分不清是哪个容器/镜像的;或者「这个 python 进程是哪来的」。
第 1 步:按名字精确匹配(主机侧恰好有 python3 系统进程,-x 排除干扰):
witr python -x
实测回显:
Target : python
Process : python (pid 849108) {forked}
User : root
Security : AppArmor docker-default (enforce)
Container : docker: witr-v034-py
Image : python:3.12-alpine
Command : python -m http.server 8142 --bind 0.0.0.0
Started : just now (Sun 2026-10-25 09:41:45 +08:00)
Why It Exists :
systemd (pid 1) → ? (original parent exited) → containerd-shim-runc-v2 (pid 849084) → python (pid 849108)
Source : docker (container)
Working Dir : /
Warnings :
• Process is running as root
• Container has no healthcheck configured
一段报告报出:容器名(witr-v034-py)、镜像(python:3.12-alpine)、AppArmor 沙箱上下文、因果链经过 containerd-shim-runc-v2、来源判定为 docker (container)。
第 2 步:按容器名直查:
witr --container witr-v034-py --short
实测回显:
systemd (pid 1) → ? (original parent exited) → containerd-shim-runc-v2 (pid 849084) → python (pid 849108)
第 3 步:按镜像名查:
witr --container python:3.12-alpine --short
实测回显(同一进程命中):
systemd (pid 1) → ? (original parent exited) → containerd-shim-runc-v2 (pid 849084) → python (pid 849108)
--container 接受容器名、容器 ID(含最短 4 字符前缀)、镜像名与 compose 项目/服务名四种标识,跨 Docker、Podman、nerdctl、K8s(crictl)、containerd、Incus/LXC/LXD 及 FreeBSD jails 统一检索;有 compose 信息时报告还会给出 compose 文件路径与重启策略(README §5.7,本机演示负载无 compose 故未显示该段)。
六、场景四:文件锁与占用定位
问题:apt 报锁被占,或想知道一个日志文件正被谁写入。
第 1 步:查文件持有者(用实测环境的临时服务日志演示,机制与 dpkg 锁相同):
witr --file /opt/data/quzuo/tmp/witr/http.log --short
实测回显:
systemd (pid 1) → ? (original parent exited) → python3 (pid 744395)
第 2 步:看完整上下文判断处置(正文回显同场景一第 2 步形态:进程卡片 + 因果链 + 来源 + 警告,此处不重复)。若是 apt 场景,官方 README 的示例即 witr --file /var/lib/dpkg/lock——命中者通常是 unattended-upgr(自动升级服务),此时不应强杀,而应等它跑完或停用自动升级,溯源报告的 Source 字段直接给出了这个判断依据。
七、场景五:进程树与批量巡检
批量巡检脚本:对服务器上的关键服务逐个溯源,汇总来源与警告数(systemctl show -p MainPID 取单元主进程,--json 喂给 jq):
#!/bin/bash
# patrol.sh —— 对关键服务做溯源巡检,汇总警告数与来源
for svc in ssh.service cron.service unattended-upgrades.service; do
unit=$(systemctl show -p MainPID --value "$svc" 2>/dev/null)
[ -n "$unit" ] && [ "$unit" != "0" ] || continue
witr --pid "$unit" --json 2>/dev/null | jq -r '"\(.ResolvedTarget) | source=\(.Source.Name // "unknown") | warnings=\(.Warnings | length) | \(.Warnings[0] // "clean")"'
done
实测回显(本机执行):
sshd | source=ssh.service | warnings=3 | Process is listening on a public interface
cron | source=cron.service | warnings=1 | Process is running as root
unattended-upgr | source=unattended-upgrades.service | warnings=2 | Process is running as root
三个服务的溯源、警告计数与首条警告一次输出。把 --json 换成 --short 则得到纯祖先链版巡检,接进 cron 即为轻量安全基线检查。
多目标混合查询(一次命令覆盖多类输入,分段输出):
witr --pid 7194 --port 8141
实测回显(分段头节选,正文同单目标报告):
----- [pid: 7194] -----
Process : sshd (pid 7194)
Source : ssh.service (systemd)
----- [port: 8141] -----
Process : python3 (pid 744395)
Source : session-17.scope (systemd)
八、场景六:JSON 集成与 AI 代理
JSON 输出(witr --pid 7194 --json,回显节选首尾,全文约 700 行):
{
"Target": {
"Type": "pid",
"Value": "7194"
},
"ResolvedTarget": "sshd",
"Process": {
"PID": 7194,
...
"Sockets": [
{
"Inode": "23045",
"Port": 22,
"Address": "0.0.0.0",
"State": "LISTEN",
"Protocol": "TCP"
},
...
],
...
},
"Source": {
"Type": "systemd",
"Name": "ssh.service",
"Description": "OpenBSD Secure Shell server",
"UnitFile": "/usr/lib/systemd/system/ssh.service",
"Details": {}
},
"Warnings": [
"Process is listening on a public interface",
"Process is running as root",
"Process is running from a suspicious working directory: /"
],
...
}
jq 提取关键字段:
witr --pid 7194 --json | jq -r '"\(.ResolvedTarget) | source=\(.Source.Name) | warnings=\(.Warnings | length)"'
实测回显:
sshd | source=ssh.service | warnings=3
退出码分支(脚本不解析文本、只看退出码):
witr --pid 7194 --short > /dev/null 2>&1; rc=$?
case $rc in
0) echo "All clear" ;;
1) echo "Warnings detected" ;;
2) echo "Process not running" ;;
3) echo "Need elevated privileges" ;;
4) echo "Invalid input or ambiguous match" ;;
5) echo "Internal error" ;;
6) echo "Cannot tell what started it" ;;
esac
实测回显(第一次,命中警告):
Warnings detected
把目标换成不存在的进程再跑,回显:
Process not running
AI 代理接入:官方 README §3 提供了给 AI 编码代理的标准话术(可直接粘进 AGENTS.md/CLAUDE.md)——端口占用用 witr --port <PORT> --json、可疑进程用 witr <name> --json、给代理的告诫是「不要从 JSON 里提取端口号去猜测配置文件,报告已含所需信息」。另有官方 Agent Skill(Claude Code:/plugin marketplace add pranshuparmar/witr)。v0.3.4 起仓库还附带 llms.txt(LLM 可读项目摘要)。代理排障时习惯串 lsof、ps、netstat、docker ps 再自行推理,witr 把这一串收敛为一条可机读命令。
九、TUI 交互实操
无参数运行 witr 即进入全屏仪表盘(等效 witr -i)。四页签能力与按键(官方 README §4,查询日期 2026-10-25):
| 页签 | 内容 | 特色 |
|---|---|---|
| Processes | 实时进程列表 + 祖先树侧栏 | 下钻进程详情 |
| Ports | 监听端口 + 持有进程侧栏 | 按 a 切 LISTEN/ALL |
| Containers | 跨 8 类运行时容器统一列表 | 详情含镜像与重启策略 |
| Locks | 系统文件锁 | 按 a 切全量打开文件 |
通用操作:Tab 切页签、鼠标点选、列排序、过滤、进程下钻(环境变量/工作目录/套接字/文件上下文)、发信号(Kill/Terminate/Pause/Resume,发送前要求确认进程名)、renice(Unix);列表按 3 秒起步的自适应节奏自动刷新。
⚠️ 实测注意:本文档在非交互环境下验证了 TUI 的进入路径与守门行为——管道/脚本中运行
witr -i会拒绝启动并返回退出码 4(实测回显见 C1 篇 8.3 节),不会挂死自动化流水线。TUI 实际按键体验请直接在服务器的交互终端上运行witr,官方还提供无需安装的浏览器沙箱教程(pranshuparmar.github.io/witr/)。
十、排障实录
以下是本次实测过程中真实踩到并解决的三类问题,覆盖「查不准」「查不了」「测不出退出码」三种典型故障。
实录 1:短名模糊匹配误伤系统进程(查不准)
现象:想溯源一个演示负载,执行 witr python,返回的是系统进程而不是目标:
Multiple matching processes found:
[1] unattended-upgr (pid 1886)
/usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal
[2] python3 (pid 744395)
python3 -m http.server 8141 --bind 0.0.0.0
Re-run with:
witr --pid <pid>
原因与解法:模糊匹配按子串搜全表,python 命中了 python3 与 unattended-upgr(解释器脚本);容器里的 python 反而没进候选(匹配范围为主机进程表 + 已知容器主进程的名称)。正确姿势:确知进程全名时加 -x 精确匹配(witr python -x,实测见场景三),或直接用 --pid / --container 指定。多候选返回本身是设计行为(exit 4),不是 bug。
实录 2:内核线程溯源返回 unknown(查不了)
现象:对 pid 2 发起溯源,退出码为 6:
witr --pid 2
实测回显:
Target : kthreadd
Process : kthreadd (pid 2) {forked}
User : root
Command : kthreadd
Started : 3 hours ago (Sun 2026-10-25 06:36:47 +08:00)
Why It Exists :
kthreadd (pid 2)
Source : unknown
Working Dir : /
Warnings :
• Process is running as root
• No known supervisor or service manager detected
• Process is running from a suspicious working directory: /
定位与结论:kthreadd 是内核线程母体,由内核自身启动,本就没有用户态父进程与服务单元——witr 如实返回 Source: unknown、警告注明「No known supervisor or service manager detected」,退出码 6(找到但无法溯源)。这不是缺陷而是「不猜」原则的体现:溯源边界诚实可见。排障脚本对退出码 6 的正确处理是「记录并转人工判断」,而非当作失败重试。同理,普通进程的原始父进程(shell)退出后,报告以 ? (original parent exited) 标注断点,有 systemd/容器证据时来源仍可判定(场景一实测)。
实录 3:管道悄悄吃掉退出码(测不出)
现象:用 for 循环批量验证退出码,所有目标都「exit=0」:
== pid 1 ==
systemd (pid 1)
exit=0 ← 错误:pid 1 实际应为 1(有警告)
== pid 403 ==
process 403 does not exist
exit=0 ← 错误:不存在的目标应为 2
原因与解法:命令写成了 witr --pid $p --short | head -3; echo exit=$?——$? 取到的是管道最后一个命令 head 的退出码(恒为 0),witr 的真实退出码被管道吞掉。修正:重定向到文件或先落变量再取码:
witr --pid 559 --short > /dev/null 2>&1; echo "exit=$?"
实测回显:
exit=2
⚠️ 通用教训:凡
set -o pipefail未开启的 bash 环境里,witr ... | grep/head后取$?都会失真。做退出码判断时保持「无管道直取」或显式PIPESTATUS。
十一、命令速查表
| 需求 | 命令 |
|---|---|
| 端口被谁占 | witr --port <PORT> |
| 端口占用一行结论 | witr --port <PORT> --short |
| 进程为什么在跑 | witr <NAME>(多匹配再 --pid 复查) |
| 精确按名查 | witr <NAME> -x |
| 看完整进程树 | witr --pid <PID> --tree |
| 看资源与 fd 明细 | witr --pid <PID> --verbose |
| 看生效环境变量 | witr --pid <PID> --env |
| 看警告摘要 | witr <目标> --warnings |
| 容器归属 | witr --container <名/ID/镜像> |
| 锁/文件被谁占用 | witr --file <路径> |
| 多目标一次查 | witr --pid <A> --port <B> --container <C> |
| 给脚本/AI | witr <目标> --json(配 jq/退出码) |
| 交互仪表盘 | witr(无参数)或 witr -i |
| 帮助/版本 | witr --help / witr --version |
十二、常见问题(FAQ)
Q1:witr <名字> 返回一堆候选怎么办?
多匹配是设计行为(列出候选、exit 4)。三种收窄方式:候选列表里挑 PID 后 witr --pid <PID>;确知全名加 -x;容器目标用 --container。
Q2:报告里 ? (original parent exited) 是异常吗?
不是。表示原始父进程(通常是启动它的 shell)已退出,witr 拒绝把收养者冒认启动者。有 systemd/容器/会话证据时来源照常判定;完全无证据时 Source: unknown、exit 6(见实录 2)。
Q3:管道里 echo $? 总是 0?
管道最后命令(head/grep 等)的退出码覆盖了 witr 的,见实录 3。判断退出码用无管道形式或 PIPESTATUS。
Q4:怎么批量巡检多台服务器?
单机跑 C1/D1 的巡检脚本(cron 定时),多机用现有运维通道分发同一脚本,汇总各机 --json 输出即可。witr 无守护进程、无服务端,天然适合这类「拉起即查」模式。
Q5:为什么本文的 PID 与我机器上的对不上?
PID 是实测时点快照。操作时先 witr <名字> 或 ss -ltnp 定位自己环境的目标 PID/端口,再套用相同命令组合,回显结构会一致(字段与分段相同,数值不同)。
十三、参考链接
- 官方 README Usage Examples:https://github.com/pranshuparmar/witr#5-usage
- 官方 README Output Behavior(退出码/JSON 契约):https://github.com/pranshuparmar/witr#7-output-behavior
- 官方浏览器交互教程(TUI 沙箱):https://pranshuparmar.github.io/witr/
- 本专栏认知篇:《witr v0.3.4 介绍与选型指南》(https://www.quzuo.net/document/76Q10ziexrrH)
- 本专栏安装篇:《Ubuntu 24.04 安装 witr v0.3.4 指南》(https://www.quzuo.net/document/SZHKmhS1Kns9)
- 本专栏参数篇:《witr v0.3.4 配置参数详解》(https://www.quzuo.net/document/Sj10gmxwsUta)