golang-spf13-cobra

golang-spf13-cobra

热门

使用 spf13/cobra 的 Golang CLI 命令树库 — cobra.Command、RunE 与 Run 对比、PersistentPreRunE 钩子链、Args 验证器(NoArgs、ExactArgs、MatchAll、自定义)、持久标志与局部标志、命令组、ValidArgsFunction、RegisterFlagCompletionFunc、ShellCompDirective、使用帮助模板自定义、man 页面和 Markdown 文档生成,以及使用 SetArgs/SetOut/SetErr 进行测试。当使用或采用 spf13/cobra,或代码库导入 `github.com/spf13/cobra` 时应用。关于与 cobra 配合使用的配置分层,请参见 `samber/cc-skills-golang@golang-spf13-viper` 技能。关于通用 CLI 架构(项目布局、退出码、信号处理、I/O 模式),请参见 `samber/cc-skills-golang@golang-cli`。

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

使用 spf13/cobra 的 Golang CLI 命令树库 — cobra.Command、RunE 与 Run 对比、PersistentPreRunE 钩子链、Args 验证器(NoArgs、ExactArgs、MatchAll、自定义)、持久标志与局部标志、命令组、ValidArgsFunction、RegisterFlagCompletionFunc、ShellCompDirective、使用帮助模板自定义、man 页面和 Markdown 文档生成,以及使用 SetArgs/SetOut/SetErr 进行测试。当使用或采用 spf13/cobra,或代码库导入 `github.com/spf13/cobra` 时应用。关于与 cobra 配合使用的配置分层,请参见 `samber/cc-skills-golang@golang-spf13-viper` 技能。关于通用 CLI 架构(项目布局、退出码、信号处理、I/O 模式),请参见 `samber/cc-skills-golang@golang-cli`。

角色: 你是一名 Go CLI 工程师,构建的命令树应具有 Unix Shell 的原生感。首先设计面向用户的界面,然后将行为连接到正确的钩子中。

模式:

  • 构建 — 从头创建新的 CLI:依次遵循命令树设置、钩子连接和标志部分。
  • 扩展 — 向现有 CLI 添加子命令、标志或补全:首先读取当前命令树,然后应用与现有结构一致的更改。
  • 审查 — 审计现有 CLI:检查常见错误表,验证 RunE 使用、OutOrStdout()、钩子链顺序和参数验证。

在 Go 中使用 spf13/cobra 构建 CLI 命令树

Cobra 是 Go CLI 应用程序的事实标准。它提供命令/子命令树、标志解析(通过 pflag)、参数验证、Shell 补全生成和文档生成。它处理配置分层——那是 viper 的工作。

官方资源:

本技能并非详尽无遗。请参考库文档和代码示例以获取更多信息。Context7 可作为发现平台提供帮助。有关 Go 包文档、版本、符号和已知漏洞,→ 参见 samber/cc-skills-golang@golang-pkg-go-dev 技能。

go get github.com/spf13/cobra@latest

Cobra 与 viper 对比

这些库执行根本不同的功能,可以独立使用。

关注点 cobra viper
拥有 命令树、标志、参数验证、补全 配置值解析
面向用户? 是 — 子命令、标志、帮助文本 否 — 纯粹是键值解析器
没有对方? 是 — 仅需标志的 CLI 只需要 cobra 是 — 读取 YAML + 环境的守护进程只需要 viper
集成点 通过 BindPFlagpflag.Flag 传递给 viper 将 cobra 标志视为最高优先级层

单独使用 cobra 当你的二进制程序接受标志和参数但不需要配置文件或环境解析时。单独使用 viper 当你有一个长时间运行的服务从 YAML + 环境读取配置且没有 CLI 子命令时。当两者都需要时同时使用——在根命令的 PersistentPreRunE 中绑定。

→ 参见 samber/cc-skills-golang@golang-spf13-viper 了解此集成的 viper 端。

命令树

每个 cobra CLI 都有一个根命令以及零个或多个通过 AddCommand 注册的子命令。根命令名称是二进制程序名称。

var rootCmd = &cobra.Command{
    Use:          "myapp",
    Short:        "一行摘要",
    SilenceUsage: true,  // ✓ 防止每次错误都显示使用帮助
    SilenceErrors: true, // ✓ 让你控制错误输出格式
}

使用 AddGroup 在帮助输出中标记子命令——在引用它们的 AddCommand 调用之前注册组;cobra 不会追溯分配组。

Run* 系列

Cobra 命令有五个按顺序执行的运行钩子:

PersistentPreRunE → PreRunE → RunE → PostRunE → PersistentPostRunE

始终使用 *E 变体——非 E 形式无法返回错误。关键规则:

  • 根命令上的 PersistentPreRunE每个子命令之前运行——用于配置初始化和身份验证检查。
  • 子命令的 PersistentPreRunE 完全替换父命令的——如果需要两者,显式调用父命令。
  • PostRunE 仅在 RunE 成功时运行。

有关完整生命周期和继承规则,请参见 commands-and-args.md

参数验证器

Cobra 在 RunE 运行之前验证位置参数。永远不要在 RunE 内部编写 len(args) 检查——这会绕过 cobra 的标准错误消息和参数计数跟踪。

内置验证器:NoArgsExactArgs(n)MinimumNArgs(n)MaximumNArgs(n)RangeArgs(min,max)OnlyValidArgsExactValidArgs(n)。使用 MatchAll(v1, v2) 组合。自定义验证器:func(cmd *cobra.Command, args []string) error

有关完整验证器集及示例和 MatchAll 模式,请参见 commands-and-args.md

标志入门

Cobra 将标志解析委托给 pflag持久标志PersistentFlags())被所有子命令继承;局部标志Flags())仅适用于声明该标志的命令。

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件路径") // 被所有子命令继承
serveCmd.Flags().IntVar(&port, "port", 8080, "监听端口")                     // 仅适用于 serveCmd
serveCmd.MarkFlagRequired("port")
serveCmd.MarkFlagsMutuallyExclusive("json", "yaml")

有关 pflag 类型、自定义标志值、标志组和 viper 绑定,请参见 flags.md

补全入门

Cobra 自动生成 Shell 补全。通过以下方式扩展:

  • ValidArgs []string — 静态位置参数补全。
  • ValidArgsFunction — 动态:func(cmd, args, toComplete string) ([]string, ShellCompDirective)。返回 ShellCompDirectiveNoFileComp 以抑制文件回退。
  • RegisterFlagCompletionFunc(name, fn) — 标志值补全。

有关 ShellCompDirective 值、注释和测试,请参见 completions.md

测试命令

通过编程方式执行命令来测试。永远不要在命令处理程序中直接使用 os.Stdout / os.Stderr——使用 cmd.OutOrStdout() / cmd.ErrOrStderr() 以便测试可以重定向输出。

func TestServeCmd(t *testing.T) {
    buf := new(bytes.Buffer)
    rootCmd.SetOut(buf)
    rootCmd.SetArgs([]string{"serve", "--port", "9090"})
    require.NoError(t, rootCmd.Execute())
    assert.Contains(t, buf.String(), "listening on :9090")
}

Cobra 在 Execute() 调用之间累积标志状态——每个测试构建一个新的命令树。有关隔离模式、黄金文件和测试补全,请参见 testing.md

最佳实践

  1. 始终使用 RunE,不要使用 RunRun 无法返回错误;唯一的出路是 os.Exit 或 panic,绕过 defer。
  2. 将配置初始化放在 PersistentPreRunE — 它在每个子命令之前运行;是 viper 绑定和身份验证检查的正确位置。
  3. 使用 Args 验证位置参数,而不是在 RunE 内部Args 提供 cobra 的标准错误消息;MatchAll 组合验证器。
  4. 对所有输出使用 cmd.OutOrStdout() / cmd.ErrOrStderr() — 直接写入 os.Stdout 无法被测试捕获。
  5. 每个测试重新创建命令树 — cobra 在同一个实例的 Execute() 调用之间累积标志状态。

常见错误

错误 失败原因 修复
使用 Run 而不是 RunE 无法返回错误——唯一的出路是 os.Exit 或 panic,绕过 defer 使用 RunE — 返回错误,让 cobra 处理退出
RunE 中编写 len(args) 检查 绕过 cobra 的标准错误消息("接受 1 个参数,收到 2 个") 在命令上声明 Args: cobra.ExactArgs(1)
直接写入 os.Stdout 测试无法捕获输出——操作系统级文件句柄无法重定向 使用 cmd.OutOrStdout() / cmd.ErrOrStderr()
子命令的 PersistentPreRunE 静默丢弃父命令的 Cobra 不会链式调用——子命令完全替换父命令的钩子 从子命令的钩子中调用 parent.PersistentPreRunE(cmd, args)
跨测试重用根命令 Cobra 累积标志状态;第二次 Execute() 看到第一次的标志 每个测试构建一个新的命令树

进一步阅读

  • commands-and-args.md — 完整的 PreRun*/PostRun* 链、每个 Args 验证器、PersistentPreRunE 继承规则
  • flags.md — pflag 类型、必需/互斥/oneRequired 组、自定义值类型、viper 绑定
  • completions.md — ShellCompDirective 集合、基于注释的补全、测试补全
  • generators.md — man 页面、Markdown、YAML、RST 文档生成;cobra-cli 脚手架
  • testing.md — 隔离模式、黄金文件、测试补全、表驱动命令测试

交叉引用

  • → 参见 samber/cc-skills-golang@golang-cli 技能了解通用 CLI 架构——项目布局、退出码、信号处理、I/O 模式
  • → 参见 samber/cc-skills-golang@golang-spf13-viper 技能了解与 cobra 配合使用的配置分层(标志 → 环境 → 文件 → 默认优先级)
  • → 参见 samber/cc-skills-golang@golang-testing 技能了解通用 Go 测试模式

如果你遇到 spf13/cobra 中的错误或意外行为,请在 https://github.com/spf13/cobra/issues 提交问题。