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:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user