golang-cli

golang-cli

热门

Golang CLI 应用程序开发。在构建、修改或审查 Go CLI 工具时使用——特别是命令结构、标志处理、配置分层、版本嵌入、退出码、I/O 模式、信号处理、Shell 补全、参数验证和 CLI 单元测试。当代码使用 cobra、viper 或 urfave/cli 时也会触发。对于 cobra 特定 API → 参见 `samber/cc-skills-golang@golang-spf13-cobra` 技能;对于 viper 配置分层 → 参见 `samber/cc-skills-golang@golang-spf13-viper` 技能。

2261Star
150Fork
更新于 2026/6/6
SKILL.md
只读
名称
golang-cli
描述

Golang CLI 应用程序开发。在构建、修改或审查 Go CLI 工具时使用——特别是命令结构、标志处理、配置分层、版本嵌入、退出码、I/O 模式、信号处理、Shell 补全、参数验证和 CLI 单元测试。当代码使用 cobra、viper 或 urfave/cli 时也会触发。对于 cobra 特定 API → 参见 `samber/cc-skills-golang@golang-spf13-cobra` 技能;对于 viper 配置分层 → 参见 `samber/cc-skills-golang@golang-spf13-viper` 技能。

角色: 你是一名 Go CLI 工程师。你构建的工具应具有 Unix Shell 的原生感——可组合、可脚本化,并且在自动化下可预测。

模式:

  • 构建 — 从头开始创建新的 CLI:按顺序遵循项目结构、根命令设置、标志绑定和版本嵌入部分。
  • 扩展 — 向现有 CLI 添加子命令、标志或补全:首先读取当前命令树,然后应用与现有结构一致的更改。
  • 审查 — 审计现有 CLI 的正确性:检查常见错误表,验证 SilenceUsage/SilenceErrors、标志到 Viper 的绑定、退出码以及 stdout/stderr 规范。

Go CLI 最佳实践

使用 Cobra + Viper 作为 Go CLI 应用程序的默认技术栈。Cobra 提供命令/子命令/标志结构,Viper 处理来自文件、环境变量和标志的配置,并自动分层。这种组合驱动了 kubectl、docker、gh、hugo 以及大多数生产级 Go CLI。

使用 Cobra 或 Viper 时,请参考库的官方文档和代码示例以获取当前 API 签名。

对于没有子命令且标志很少的简单单一用途工具,stdlib flag 就足够了。

快速参考

关注点 包 / 工具
命令和标志 github.com/spf13/cobra
配置 github.com/spf13/viper
标志解析 github.com/spf13/pflag(通过 Cobra)
彩色输出 github.com/fatih/color
表格输出 github.com/olekukonko/tablewriter
交互式提示 github.com/charmbracelet/bubbletea
版本注入 go build -ldflags
分发 goreleaser

项目结构

cmd/myapp/ 中组织 CLI 命令,每个命令一个文件。保持 main.go 最小化——它只调用 Execute()

myapp/
├── cmd/
│   └── myapp/
│       ├── main.go              # package main,只调用 Execute()
│       ├── root.go              # 根命令 + Viper 初始化
│       ├── serve.go             # "serve" 子命令
│       ├── migrate.go           # "migrate" 子命令
│       └── version.go           # "version" 子命令
├── go.mod
└── go.sum

main.go 应保持最小化——参见 assets/examples/main.go

根命令设置

根命令通过 PersistentPreRunE 初始化 Viper 配置并设置全局行为。参见 assets/examples/root.go

关键点:

  • SilenceUsage: true 必须设置——防止在每个错误上打印完整的用法文本
  • SilenceErrors: true 必须设置——让你自己控制错误输出格式
  • PersistentPreRunE 在每个子命令之前运行,因此配置始终已初始化
  • 日志输出到 stderr,输出到 stdout

子命令

通过在 cmd/myapp/ 中创建单独的文件并在 init() 中注册它们来添加子命令。参见 assets/examples/serve.go 获取包含命令组的完整子命令示例。

标志

参见 assets/examples/flags.go 获取所有标志模式:

持久标志 vs 本地标志

  • 持久 标志被所有子命令继承(例如 --config
  • 本地 标志仅适用于定义它们的命令(例如 --port

必需标志

使用 MarkFlagRequiredMarkFlagsMutuallyExclusiveMarkFlagsOneRequired 进行标志约束。

使用 RegisterFlagCompletionFunc 进行标志验证

为标志值提供补全建议。

始终将标志绑定到 Viper

这确保 viper.GetInt("port") 返回标志值、环境变量 MYAPP_PORT 或配置文件值——以优先级最高的为准。

参数验证

Cobra 为位置参数提供内置验证器。参见 assets/examples/args.go 获取内置和自定义验证示例。

验证器 描述
cobra.NoArgs 如果提供了任何参数则失败
cobra.ExactArgs(n) 需要恰好 n 个参数
cobra.MinimumNArgs(n) 需要至少 n 个参数
cobra.MaximumNArgs(n) 最多允许 n 个参数
cobra.RangeArgs(min, max) 需要介于 min 和 max 之间
cobra.ExactValidArgs(n) 恰好 n 个参数,必须在 ValidArgs 中

使用 Viper 进行配置

Viper 按以下顺序解析配置值(从高到低优先级):

  1. CLI 标志(显式用户输入)
  2. 环境变量(部署配置)
  3. 配置文件(持久设置)
  4. 默认值(在代码中设置)

参见 assets/examples/config.go 获取完整的 Viper 集成,包括结构体解组和配置文件监视。

示例配置文件 (.myapp.yaml)

port: 8080
host: localhost
log-level: info
database:
  dsn: postgres://localhost:5432/myapp
  max-conn: 25

使用上述设置,以下所有方式等效:

  • 标志:--port 9090
  • 环境变量:MYAPP_PORT=9090
  • 配置文件:port: 9090

版本和构建信息

版本应使用 ldflags 在编译时嵌入。参见 assets/examples/version.go 获取版本命令和构建说明。

退出码

退出码必须遵循 Unix 约定:

代码 含义 使用场景
0 成功 操作正常完成
1 一般错误 运行时失败
2 用法错误 无效的标志或参数
64-78 BSD sysexits 特定错误类别
126 无法执行 权限被拒绝
127 命令未找到 缺少依赖
128+N 信号 N 被信号终止(例如 130 = SIGINT)

参见 assets/examples/exit_codes.go 获取将错误映射到退出码的模式。

I/O 模式

参见 assets/examples/output.go 获取所有 I/O 模式:

  • stdout vs stderr:永远不要将诊断输出写入 stdout——stdout 用于程序输出(可管道化),stderr 用于日志/错误/诊断
  • 检测管道 vs 终端:检查 stdout 上的 os.ModeCharDevice
  • 机器可读输出:支持 --output 标志用于 table/json/plain 格式
  • 颜色:使用 fatih/color,当输出不是终端时自动禁用

信号处理

信号处理必须使用 signal.NotifyContext 通过上下文传播取消。参见 assets/examples/signal.go 获取优雅的 HTTP 服务器关闭。

Shell 补全

Cobra 自动为 bash、zsh、fish 和 PowerShell 生成补全。参见 assets/examples/completion.go 获取补全命令和自定义标志/参数补全。

测试 CLI 命令

通过编程方式执行命令并捕获输出来测试命令。参见 assets/examples/cli_test.go

在命令中使用 cmd.OutOrStdout()cmd.ErrOrStderr()(而不是 os.Stdout / os.Stderr),以便输出可以在测试中被捕获。

常见错误

错误 修复
直接写入 os.Stdout 测试无法捕获输出。使用 cmd.OutOrStdout(),测试可以将其重定向到缓冲区
RunE 内部调用 os.Exit() Cobra 的错误处理、延迟函数和清理代码永远不会运行。返回错误,让 main() 决定
未将标志绑定到 Viper 标志无法通过环境变量/配置文件配置。为每个可配置标志调用 viper.BindPFlag
缺少 viper.SetEnvPrefix PORT 与其他工具冲突。使用前缀(MYAPP_PORT)来命名空间环境变量
日志输出到 stdout Unix 管道链式传递 stdout——日志会破坏下一个程序的数据流。日志应输出到 stderr
每个错误都打印用法 每个错误都打印完整帮助文本是噪音。设置 SilenceUsage: true,将完整用法保留给 --help
配置文件必需 没有配置文件的用户会崩溃。忽略 viper.ConfigFileNotFoundError——配置应该是可选的
未使用 PersistentPreRunE 配置初始化必须在任何子命令之前发生。使用根命令的 PersistentPreRunE
硬编码版本字符串 版本会与标签不同步。通过 ldflags 在构建时从 git 标签注入
不支持 --output 格式 脚本无法解析人类可读的输出。添加 JSON/table/plain 格式供机器消费

相关技能

参见 samber/cc-skills-golang@golang-project-layoutsamber/cc-skills-golang@golang-dependency-injectionsamber/cc-skills-golang@golang-testingsamber/cc-skills-golang@golang-design-patterns 技能。