odboy f5ae80c1a8 feat: 将更多 Docker 命令改为内置实现,统一按名称/ID 解析目标
- exec/rm/rmi/stop/restart 支持名称、ID 或唯一 ID 前缀,目标不存在或
  前缀有歧义时在调用 docker 前报错;exec 默认 -it 并以 -- 分隔命令
- logs 新增 --since/--grep/--save;df 改为按占用排序的可读排行;
  stats 默认单次快照,无参数时统计全部运行中容器
- 破坏性确认:Command.ConfirmBuiltin 提供将删除对象的清单,
  rm/rmi 无 --yes 时列清单并退出码 2,拒绝 -f,运行中容器提示先 stop
- 抽出 executor/dockertargets.go 统一镜像/容器解析与 docker 调用
2026-11-01 08:01:12 +08:00

kopsc

跨平台命令助手。用一条平台无关的意图命令,在受支持的平台上执行对应的底层命令:

$ kopsc docker clearsafe --dry-run
[dry-run] 未执行任何命令
将执行: docker system prune -f
平台:   linux/amd64, distro=centos, runtimes=docker
方式:   直接执行
命中:   变体 #1(2 分)Linux 上的 Docker 运行时
依据:   匹配 os=linux, runtime=docker (+2)

在 Linux 上它执行 docker system prune -f,macOS / Windows 上使用 Docker Desktop 的同一命令。 你只记住一个意图,kopsc 负责挑出当前平台可用的那条命令。

构建

本机构建:

go build -o bin/kopsc ./cmd/kopsc

跨平台编译脚本

scripts/build 是纯 Go 编写的构建脚本,用 go run 运行,不依赖 make / bash,在 Windows、macOS、Linux 上行为一致:

go run ./scripts/build                       # 默认编译全部 6 个目标到 dist/
go run ./scripts/build -host                 # 只编译当前 OS/ARCH
go run ./scripts/build -targets linux/amd64,windows/arm64
go run ./scripts/build -version 1.2.3        # 指定版本(默认 git describe --tags --always --dirty)
go run ./scripts/build -out bin -no-strip    # 自定义输出目录、保留符号表

默认目标矩阵:linux/{amd64,arm64}、darwin/{amd64,arm64}、windows/{amd64,arm64}。 脚本统一设置 CGO_ENABLED=0(纯 Go 静态产物)与 -trimpath,产物命名为 kopsc-<version>-<os>-<arch>[.exe],任一目标失败即以非零码退出。

等价的单条命令写法:

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath \
  -ldflags "-s -w -X main.version=1.0.0" -o dist/kopsc-linux-amd64 ./cmd/kopsc

用法

kopsc docker ps                   # 列出运行中的容器
kopsc docker psall                # 含已停止的容器
kopsc docker logs -f web          # 查看日志(容器名必填,选项可透传)
kopsc docker exec web sh          # 进入容器(默认就是交互模式,无需 -it)
kopsc docker compose up           # 启动 Compose 服务
kopsc docker clearall --yes       # 深度清理(破坏性命令,必须显式 --yes)
kopsc docker save alpine:3.20     # 自动保存为 alpine_3.20.tar(传镜像 ID 会先解析出名称)
kopsc docker load alpine_3.20.tar # 等价 docker load < alpine_3.20.tar

kopsc docker clearsafe            # 直接执行当前平台对应的清理命令
kopsc docker clearsafe --dry-run  # 只打印将执行的命令与判定依据
kopsc docker clearsafe -h         # 该意图的说明、当前平台命中结果与选项
kopsc docker clearsafe -- --filter until=24h   # -- 之后的参数原样透传给底层命令

kopsc diagnose web                 # 自动识别 Docker 容器或 K8s Pod,给出根因
kopsc diagnose api-0 -n prod       # K8s:本命令里 -n 表示 namespace
kopsc diagnose api-0 --kind k8s --context staging
kopsc diagnose web --lines 200 --no-events

kopsc net pids 8080                  # 等价 lsof -ti:8080,输出占用端口的 PID
kopsc net ports                      # 列出监听中的端口

kopsc disk usage /var                # 磁盘占用排行(du -x --max-depth=1 -h | sort -hr 的跨平台版)
kopsc disk usage --path /var         # 同上,用命名参数指定路径
kopsc disk usage /var --dry-run      # 预览(内置命令同样支持)
kopsc proc nohup ./server            # 后台运行命令,输出写日志(等价 nohup ./server > server.out 2>&1 &)

kopsc list                        # 全部命令及当前平台可用性
kopsc explain docker.clearsafe    # 匹配过程、全部候选及各自被选中/拒绝的原因
kopsc platform                    # 探测到的 OS/ARCH/发行版/运行时
kopsc version                     # 版本与平台

磁盘占用排行(disk.usage)

kopsc disk usage <路径> 等价于 du -x --max-depth=1 -h <路径> | sort -hr,但由 kopsc 内置实现,因此 Linux、macOS、Windows 输出完全一致,不依赖 du、sort 或 PowerShell:

$ kopsc disk usage /var
1.2G     /var/lib
800.0M   /var/cache
4.0K     /var/tmp
0        /var/opt
总计      2.0G     /var
  • 路径必填:位置参数(/var)或 --path /var、--path=/var 均可,一次一个。
  • 统计每个直接子项:普通文件按自身大小,目录递归累计其下普通文件的大小。
  • 不跟随符号链接;Unix 上通过设备号实现 -x(不跨文件系统),Windows 上不跟随 reparse point,目录遍历本身不会跨越卷。
  • 输出按大小降序,大小采用与 du -h 相同的二进制单位(1.0K、1.5M、2.0G)。
  • 末尾输出该路径的合计行。

选项解析规则

叶子命令禁用了 cobra 的 flag 解析,自行处理:

参数 行为
-n, --dry-run, --dry-run=true 置为预览模式,不执行。例外:kopsc diagnose 中 -n 表示 namespace,预览请用 --dry-run
--dry-run=false 关闭预览模式
-y, --yes 确认执行破坏性命令;未提供时只打印影响范围并以退出码 2 结束
-h, --help 打印该命令的帮助
-- 其后的所有参数原样透传给底层命令(并全部计入位置参数)
其它任何参数 原样透传给底层命令(包括未知 flag)

因此底层命令的 flag 不会与 kopsc 自身的选项冲突。

需要容器名/镜像名的命令会做必填校验(例如 kopsc docker logs 缺容器名时直接报错, 并给出 kopsc docker logs <容器> 的用法),避免把参数错误留给底层命令才发现。

内置命令

Docker(kopsc docker <子命令>)

分组 命令
只读 ps、psall、images、stats、df、info、version、logs、inspect、networks、volumes
生命周期 start、stop、restart、rm、rmi、exec、pull、tag、save、load、cp
清理 clearsafe、pruneimages、prunecontainers、prunenets、prunebuilder、prunevolumes、clearall
Compose compose up、compose down、compose ps、compose logs
  • 所有 Docker 命令在三个平台都调用 docker(macOS / Windows 上即 Docker Desktop)。
  • logs、inspect、start、stop、restart、rm、rmi、exec、pull、tag、save、load、cp 需要位置参数,缺失时直接报错并给出用法。
  • 破坏性命令是 rm、rmi、clearall(system prune -a -f)与 prunevolumes(volume prune -f): 未带 --yes 时先列出将删除的对象、再打印将执行的命令,并以退出码 2 结束。
  • docker compose 对应 Compose v2。
  • docker clearsafe 的安全契约:只加 -f 以便无人值守执行,不加 -a(不清理全部未使用镜像), 不加 --volumes(不删除卷),仅清理停止的容器、未使用的网络与悬空镜像。

镜像导出与导入(docker save / docker load)

kopsc docker save <镜像名|镜像ID> 等价于 docker save <镜像> -o <镜像名>.tar,归档文件自动命名:

kopsc docker save alpine:3.20                      # → alpine_3.20.tar
kopsc docker save 9c6f07244728                     # 按 ID 查到 alpine:3.20,同样保存为 alpine_3.20.tar
kopsc docker save sha256:9c6f07244728 --platform linux/arm64
kopsc docker load alpine_3.20.tar                  # 等价 docker load < alpine_3.20.tar
kopsc docker load a.tar b.tar                      # 一次导入多个
  • 文件名取自镜像的仓库名 + 标签:标签前的冒号、路径分隔符 / 等无法用于文件名的字符改写为下划线, 例如 registry.example.com:5000/team/app:1.0 → registry.example.com_5000_team_app_1.0.tar; 未打标签的镜像没有名称可依据,直接报错并提示先 docker tag。
  • 传入镜像 ID(可省略 sha256: 前缀,也可用唯一前缀)时,先向运行时查询它对应的镜像名称,再据此命名。 同一 ID 有多个名称时要求直接指定名称,避免文件名含糊。
  • --platform 等运行时选项原样透传给底层运行时。
  • docker load 的归档文件是位置参数(对应原来的 docker load < xxx.tar),可一次给出多个; -i/--input 仍然可用,传 - 仍表示从标准输入读取。

容器与镜像的简写(exec / rm / rmi / stop / restart)

这几条命令按 kopsc 的意图做了解析,不再要求手写 docker 的细节:

kopsc docker exec web sh            # 等价 docker exec -it web -- sh(-it 已是默认)
kopsc docker exec -u 0 web id       # 容器名之前的 -u/-w/-e 照常透传给 docker
kopsc docker stop 3f2a1b           # 按 ID 前缀找到容器并停止
kopsc docker rm db                 # 删除已停止的容器;运行中的会提示先 stop
kopsc docker rmi sha256:5ca7b1d5e2fc   # 按 ID 或唯一前缀删除镜像
  • 容器可按名称、ID 或唯一 ID 前缀定位;镜像可按名称、ID、sha256:ID 或唯一前缀定位。 目标不存在或前缀有歧义时,kopsc 直接报错,不会调用 docker。
  • exec 默认交互,命令之后的参数一律作为容器内命令,因此命令自己的 -i、-p 不会被当成 docker 选项; 显式传 -i/-t/-it 会报错提示「已默认交互」。
  • rm/rmi 属于破坏性命令:不带 --yes 时先列出将删除的对象(容器显示 ID/名称/镜像/状态, 镜像显示 镜像/ID/创建/大小)再以退出码 2 结束;不再接受 -f,强删请用 --yes。
  • rm 遇到运行中的容器会提示先 kopsc docker stop <名称>,不会替你强删。

日志、占用与实时状态(logs / df / stats)

kopsc docker logs web --since 2h           # 只看最近 2 小时
kopsc docker logs --grep ERROR web         # 只看匹配行(kopsc 选项写在容器名之前)
kopsc docker logs --save /var/log/web.log web -f   # 同时落盘,-f 仍交给 docker
kopsc docker df                            # 按占用大小排序的类型排行
kopsc docker stats                         # 单次快照,默认统计全部运行中容器
kopsc docker stats web --format '{{.CPUPerc}}'
  • --since/--grep/--save 是 kopsc 私有选项,必须写在容器名之前;容器名之后的参数原样交给 docker。
  • df 输出「类型 / 项 / 可清理 / 大小 / 可回收」,末尾附总计与可回收提示。
  • stats 总是单次快照(--no-stream),不会再刷屏;给容器名时只统计这些,且要求容器正在运行。

容器/Pod 诊断(diagnose)

kopsc diagnose <名称> 自动判断目标是 Docker 容器还是 Kubernetes Pod,输出状态、根因、依据、建议,并附最近日志与事件。只读,不修改任何对象。

目标:   web
来源:   Docker (docker)
状态:   exited  ExitCode=137  OOMKilled=true  RestartCount=3
镜像:   nginx:1.25
判定:   ❌ 容器因内存超限被 OOM kill
依据:   State.Status=exited, ExitCode=137, OOMKilled=true, RestartCount=3
建议:   提高容器的内存限制,或降低应用峰值内存;可用 kopsc docker stats 观察

最近日志 (50 行):
...
最近事件 (1h):
...
参数 说明
--kind docker|k8s 强制来源;默认自动识别:先 docker inspect,未命中再 kubectl get pod
-n, -N, --namespace 仅 diagnose:Kubernetes 命名空间。默认取 kubeconfig 当前上下文,未设置则为 default
--context kubectl 上下文
-c, --container 多容器 Pod 只看指定容器(默认输出全部容器)
--lines N 日志行数,默认 50
--no-logs / --no-events 跳过日志 / 事件采集
--dry-run 只预览将执行的诊断动作,不调用 docker/kubectl

-n 只在 kopsc diagnose 中表示 namespace;其它命令里 -n 仍是 --dry-run 的简写。 Docker 模式下传 --namespace/--context 会直接报错,不会静默忽略。

判定覆盖:Docker 的 OOMKilled、ExitCode 137/143/126/127、正常退出、重启循环、unhealthy、created/paused/dead; Kubernetes 的 CrashLoopBackOff、ImagePullBackOff/ErrImagePull、CreateContainerConfigError、OOMKilled、 Pending(结合 FailedScheduling/FailedMount 事件)、Evicted 等。

端口占用查询(net.pids)

kopsc net pids <端口> 等价 lsof -ti:<端口>:输出占用该端口的进程 PID,每行一个、升序、去重;没有进程占用时无输出并正常退出。

kopsc net pids 8080
kopsc net pids --port 8080
kopsc net pids 8080 --dry-run

底层按平台自动选择,但对外的命令与输出保持一致:

平台 依次尝试的底层来源
Linux lsof -ti:<port> → ss -H -t -u -n -p(解析 pid=) → 读 /proc/net/* 与 /proc/*/fd(纯内置,零外部依赖)
macOS lsof -ti:<port>(系统自带,无 /proc 回退)
Windows netstat -ano(解析本地地址与 PID)
  • 端口必须是 1–65535;非法端口直接报错,不执行任何外部命令。
  • lsof 无匹配时退出码为 1 且无输出,kopsc 视为“没有进程占用”,返回 0。
  • 只读命令,不需要 --yes。

其它

命令 说明 平台变体
diagnose 容器/Pod 状态根因诊断(自动识别 Docker 与 Kubernetes) Linux/macOS/Windows 均使用 kopsc 内置实现
net.pids 查找占用指定端口的进程 PID(等价 lsof -ti:<端口>) Linux lsof → ss → /proc;macOS lsof;Windows netstat -ano。三平台命令与输出一致(PID 升序、每行一个)
disk.usage 目录占用排行(等价 du -x --max-depth=1 -h <路径> | sort -hr) Linux/macOS/Windows 均使用 kopsc 内置实现,保证输出一致
net.ports 列出监听中的端口 Linux ss -tulnp、macOS lsof -iTCP -sTCP:LISTEN -nP、Windows netstat -ano
pkg.refreshindex 刷新包管理器索引 centos dnf makecache、ID_LIKE rhel/fedora dnf makecache、ID_LIKE debian/ubuntu apt-get update、alpine apk update、macOS brew update、Windows winget source update
proc.nohup 后台运行命令,输出写入日志(等价 nohup <命令> > output.log 2>&1 &) Linux/macOS 内置实现(新会话 + 日志追加)、Windows 内置实现(DETACHED_PROCESS + 日志追加)

后台运行(proc.nohup)

kopsc proc nohup <命令> [参数...] 等价于 nohup <命令> > output.log 2>&1 &:启动命令后立刻返回,关闭终端不会终止它。

kopsc proc nohup ./server -port 8080                    # 日志写到 ./server.out(同 nohup.out)
kopsc proc nohup -o /var/log/server.log ./server        # 指定日志
kopsc proc nohup -C /srv/app --env=APP_ENV=prod ./run.sh  # 指定工作目录与环境变量
kopsc proc nohup -w ./migrate.sh                        # 等它结束,并回显日志、透传退出码
kopsc proc nohup --dry-run ./server                     # 只打印判定依据
  • 标准输入接到空设备;标准输出与标准错误都追加写入日志文件(>> 语义),并自动创建日志目录。
  • Unix 下子进程进入新会话、无控制终端(相当于 setsid),Windows 下以 DETACHED_PROCESS 启动。
  • 选项只在命令之前解析:kopsc proc nohup ffmpeg -i in.mp4 中的 -i 原样给 ffmpeg, 不会被 kopsc 截获(因此命令自身的 -n/--dry-run 不会触发 kopsc 的预览)。 需要预览时把 --dry-run 写在命令之前、或用 -- 分隔。
  • 不经过 shell,>、|、&& 需要显式写 shell:kopsc proc nohup sh -c 'a | b > c'。
选项 说明
-o, --out, --output 日志文件,默认 <程序名>.out
-C, --dir, --chdir 子进程工作目录(相对日志路径也基于它),默认当前目录
-E, --env NAME=VALUE 追加环境变量,可重复
-q, --quiet 不打印启动摘要(PID、日志路径)
-w, --wait 前台等待命令退出,回显其写入的日志内容并透传退出码
--detach / --no-detach 默认脱离会话;--no-detach 保留当前会话(类似只加 &)

平台识别

kopsc platform 展示探测结果,来源:

  • OS/ARCH:编译目标 runtime.GOOS / runtime.GOARCH
  • Linux 发行版:/etc/os-release 的 ID 与 ID_LIKE(文件缺失时视为未知,不报错)
  • 容器运行时:exec.LookPath 探测 docker

匹配规则

每个命令由若干变体组成,变体声明它适用的环境约束(OS、Arch、Distro、 DistroLike、Runtime,留空表示不限制)。

  1. 变体声明的每一个约束都必须满足,否则该变体被排除。
  2. 满足的变体按特异性打分:Distro 精确命中 +3,ID_LIKE 命中 +2,Runtime 命中 +2,无约束的通用变体 0 分。
  3. 取最高分。并列时先按运行时偏好(有运行时约束者优先,且 docker 在前),再按声明约束的数量(更具体者优先)。
  4. 仍并列则报歧义错误;无任何变体匹配则报无匹配错误,并提示 kopsc explain <命令>。
  5. 取 --dry-run 或 explain 可看到每个候选的得分与被拒原因。

例如 CentOS 上执行 kopsc explain docker.clearsafe:

✓ #0 2 分  匹配 os=linux, runtime=docker (+2) -> docker system prune -f
✗ #1 0 分  os=darwin 不匹配(当前 linux)      -> docker system prune -f
✗ #2 0 分  os=windows 不匹配(当前 linux)     -> docker system prune -f

新增命令或平台变体

内置映射位于 internal/registry/builtin.go(纯 Go 注册表,无外部配置文件)。 新增一个变体即向对应 Command.Variants 追加一项:

{
    Match: Match{OS: "linux", Distro: "rocky", Runtime: "docker"},
    Argv:  []string{"docker", "system", "prune", "-f"},
    Note:  "Rocky Linux 上的 Docker",
},
  • Argv[0] 是程序,其余是参数;需要 shell 语义时设 Shell: true(Linux/macOS 用 sh -c,Windows 用 cmd /c)。
  • 当平台没有可移植的系统命令时,用 Builtin 代替 Argv(二者互斥): Builtin: "disk.usage" 指向 internal/executor 中注册的实现, 这样三平台共用同一份逻辑。新增内置动作需在 executor.builtins 中注册。
  • 新增命令时给 Command 填 ID(点号分隔,决定 CLI 层级)、Summary、Action,别名不得重复。
  • 提交前跑 go test ./...:registry.Validate() 与快照测试会拦截重复 ID/别名、空变体、空 argv。

开发

gofmt -l .        # 应无输出
go vet ./...
go test ./...

包结构:

cmd/kopsc            入口,注入版本号
internal/platform    环境探测(可注入,便于测试)
internal/registry    命令模型、校验、内置注册表
internal/resolve     变体打分与选择
internal/executor    进程执行、内置动作、dry-run、退出码透传
internal/diagnose    Docker 与 Kubernetes 的状态根因诊断(内置动作)
internal/cli         cobra 命令树与辅助命令
scripts/build        跨平台构建脚本(go run ./scripts/build)
S
Description
linux命令助手,记忆解放
Readme Apache-2.0
774 KiB
Languages
Go 100%