feat: 新增 proc wait,等待进程退出

- 每秒检查一次目标进程,存在则继续等待,直到消失为止
- 关键字在进程名与命令行中匹配(忽略大小写),多个关键字为逻辑与;
  --pid 可直接指定进程 ID(逗号分隔可多个)
- 默认不超时,--timeout 限时(退出码 2 并列出残留 PID),--once 只检查一次
- 三平台实现:Linux 读 /proc(回退 ps)、macOS 用 ps、Windows 用 tasklist
- ExitError 改为优先展示具体原因,不再只显示「命令以状态码 N 退出」
- 文档:README.proc.md 新增完整小节,主 README 补充示例与速查
This commit is contained in:
2026-11-01 09:01:32 +08:00
parent 3725f8307a
commit 14817bcfe7
10 changed files with 837 additions and 3 deletions
+28
View File
@@ -424,6 +424,34 @@ func Builtin() *Registry {
},
},
},
{
ID: "proc.wait",
Group: "proc",
Summary: "等待进程退出(关键字可多个,逻辑与;也可按 PID)",
Action: "每秒检查一次目标进程是否还在:存在就继续等,直到它消失为止。" +
"关键字在进程名与命令行中匹配(忽略大小写),多个关键字需全部命中(逻辑与);" +
"--pid 可直接指定进程 ID(逗号分隔可多个),与关键字同时给出时任一方仍存在都会继续等待。" +
"默认一直等到进程退出;--timeout 可限时,超时以退出码 2 结束;--once 只检查一次不等待。" +
"只读,不修改任何进程",
Usage: "<关键字>... [--pid PID]...",
Variants: []Variant{
{
Match: Match{OS: "linux"},
Builtin: "proc.wait",
Note: "Linux 内置实现(读 /proc,回退 ps)",
},
{
Match: Match{OS: "darwin"},
Builtin: "proc.wait",
Note: "macOS 内置实现(ps -eo pid=,args=)",
},
{
Match: Match{OS: "windows"},
Builtin: "proc.wait",
Note: "Windows 内置实现(tasklist,关键字只能匹配进程名)",
},
},
},
{
ID: "pkg.refreshindex",
Group: "pkg",
+28 -1
View File
@@ -25,7 +25,7 @@ func TestBuiltinIDsStable(t *testing.T) {
"docker.prunebuilder", "docker.prunevolumes", "docker.clearall",
"docker.compose.up", "docker.compose.down", "docker.compose.ps", "docker.compose.logs",
"diagnose",
"disk.usage", "net.pids", "net.ports", "proc.nohup", "pkg.refreshindex",
"disk.usage", "net.pids", "net.ports", "proc.nohup", "proc.wait", "pkg.refreshindex",
}
if got := reg.IDs(); !reflect.DeepEqual(got, want) {
t.Fatalf("IDs() =\n%v\nwant\n%v", got, want)
@@ -346,3 +346,30 @@ func TestMatchIsComparable(t *testing.T) {
t.Fatal("Match should be a comparable plain struct value")
}
}
func TestProcWaitIsBuiltinEverywhere(t *testing.T) {
reg := Builtin()
cmd, ok := reg.Lookup("proc.wait")
if !ok {
t.Fatal("proc.wait not found")
}
// The command is read-only: it must not demand --yes.
if cmd.RequiresYes {
t.Error("proc.wait only observes processes and must not require --yes")
}
if cmd.Usage == "" {
t.Error("proc.wait should document its arguments in Usage")
}
seen := map[string]bool{}
for _, v := range cmd.Variants {
if v.Builtin != "proc.wait" || len(v.Argv) != 0 {
t.Errorf("variant for %s should be the proc.wait builtin, got %+v", v.Match.OS, v)
}
seen[v.Match.OS] = true
}
for _, osName := range []string{"linux", "darwin", "windows"} {
if !seen[osName] {
t.Errorf("proc.wait lacks a %s variant", osName)
}
}
}