Cobra使用指南

一:Cobra 是什么

Cobra 是 Go 语言中最流行的命令行框架,Kubernetes、Hugo、GitHub CLI 等知名项目都在使用它。

它帮你解决 CLI 开发中的所有"脏活":

特性 说明
子命令 app serverapp fetch 这样的层级命令
POSIX 标志 完整支持短标志 -v、长标志 --verbose,由 pflag 提供
嵌套子命令 kubectl get pods 式的多级命令
全局/本地标志 标志可以只属于一个命令,也可以传递给所有子命令
智能提示 输错命令时提示 app srver... did you mean "app server"?
自动生成帮助 -h/--help 自动可用
Shell 补全 自动为 bash、zsh、fish、PowerShell 生成补全脚本
手册生成 自动生成 man page
命令别名 不破坏旧名称的情况下改名

1.1 三个核心概念

Cobra 的世界观由三样东西组成:

css 复制代码
APPNAME  COMMAND  ARG  --FLAG
  • Command(命令) :代表"动作",如 serverclone
  • Arg(参数) :动作作用的对象,如 git clone URL 中的 URL
  • Flag(标志) :动作的修饰符,如 --port=1313

好的 CLI 读起来像一句话,例如:

css 复制代码
hugo server --port=1313
git clone URL --bare

1.2 安装

bash 复制代码
go get -u github.com/spf13/cobra@latest

代码中导入:

arduino 复制代码
import "github.com/spf13/cobra"

可选:官方脚手架工具(自动生成项目骨架):

bash 复制代码
go install github.com/spf13/cobra-cli@latest
cobra-cli init myapp

二:环境准备与第一个程序

2.1 前置条件

●已安装 Go(建议 1.21+,库本身要求 go 1.15+)

●会使用终端

2.2 创建项目

bash 复制代码
mkdir demo && cd demo
go mod init demo
go get github.com/spf13/cobra@latest

2.3 第一个 CLI

创建 main.go:

go 复制代码
package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "demo",
        Short: "demo 是一个示例程序",
        Long:  "一个用于学习 cobra 的演示应用,长描述会显示在 help 输出中。",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("你好,Cobra!")
        },
    }

    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

运行:

go 复制代码
go run main.go          # 输出:你好,Cobra!
go run main.go --help   # 查看自动生成的帮助

帮助输出大致如下:

bash 复制代码
一个用于学习 cobra 的演示应用...

Usage:
  demo [flags]

Flags:
  -h, --help   help for demo

关键点 :Execute() 是唯一入口。它从 os.Args[1:] 读取参数,在命令树中找到匹配的命令并执行。出错时返回 error。


三:核心概念------命令、参数、标志

3.1 Command 结构体常用字段

go 复制代码
&cobra.Command{
    Use:     "add [-F file | -D dir]... [-f format] profile", // 一行用法
    Aliases: []string{"a", "create"},                          // 别名
    Short:   "添加一个配置项",       // 短描述(显示在父命令 help 列表中)
    Long:    "add 命令的详细说明......",  // 长描述(显示在自己 --help 时)
    Example: "demo add --file a.txt", // 示例
    Version: "1.0.0",                 // 定义后自动获得 --version
    Run:     func(cmd *cobra.Command, args []string) { ... },
}
  • Use第一个单词就是命令名,其余部分是给用户看的参数格式说明
  • Short 出现在 demo --help 的命令列表里;Long 出现在 demo add --help
  • 只写 Run 不写 RunE,错误处理用 panic 或自己打印

3.2 添加子命令

go 复制代码
var rootCmd = &cobra.Command{Use: "demo", Short: "演示程序"}

var helloCmd = &cobra.Command{
    Use:   "hello [名字]",
    Short: "向某人打招呼",
    Args:  cobra.MaximumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        name := "世界"
        if len(args) > 0 {
            name = args[0]
        }
        fmt.Printf("你好, %s!\n", name)
    },
}

func init() {
    rootCmd.AddCommand(helloCmd)
}

效果:

ruby 复制代码
$ demo hello
你好, 世界!
$ demo hello 小明
你好, 小明!

3.3 组织代码的推荐结构

实际项目推荐一个命令一个文件:

go 复制代码
demo/
├── main.go          // 只调用 cmd.Execute()
├── cmd/
│   ├── root.go      // 根命令 + Execute()
│   ├── hello.go     // hello 子命令
│   └── serve.go     // serve 子命令
└── go.mod

main.go:

go 复制代码
package main

import "demo/cmd"

func main() {
    cmd.Execute()
}

cmd/root.go:

go 复制代码
package cmd

import "github.com/spf13/cobra"

var rootCmd = &cobra.Command{
    Use:   "demo",
    Short: "演示程序",
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        panic(err) // 或 os.Exit(1)
    }
}

cmd/hello.go:

go 复制代码
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var helloCmd = &cobra.Command{
    Use: "hello",
    Short: "打招呼",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("hello")
    },
}

func init() {
    rootCmd.AddCommand(helloCmd)
}

3.4 子命令分组显示

命令多了以后,可以用 AddGroup 把 help 输出里的子命令分组:

php 复制代码
rootCmd.AddGroup(&cobra.Group{ID: "build", Title: "构建相关:"})
rootCmd.AddGroup(&cobra.Group{ID: "test", Title: "测试相关:"})

buildCmd.GroupID = "build"
testCmd.GroupID  = "test"

help 效果:

makefile 复制代码
构建相关:
  build   构建项目

测试相关:
  test    运行测试

注意:子命令的 GroupID 必须是父命令已通过 AddGroup 定义的 ID,否则执行时直接 panic(源码 checkCommandGroups 的行为)。


四:标志(Flags)详解

标志功能由 pflag 提供(标准库 flag 的 POSIX 兼容 fork)。

4.1 定义标志

csharp 复制代码
var verbose bool
var port int
var name string

func init() {
    helloCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "是否输出详细日志")
    helloCmd.Flags().IntVarP(&port, "port", "p", 8080, "服务端口")
    helloCmd.Flags().StringVar(&name, "name", "默认名", "名称")
}

命名规则:

函数 含义
XxxVarP(&v, "long", "s", 默认值, "说明") 绑定变量 + 短标志
XxxVar(&v, "long", 默认值, "说明") 绑定变量,无短标志
Xxx("long", 默认值, "说明") *T 返回指针,不绑定变量

支持的类型:BoolIntInt64Float64StringDurationStringSliceStringArrayCount 等。

4.2 使用标志

以下写法等价(长标志):

css 复制代码
demo hello --name=小明
demo hello --name 小明

短标志:

ini 复制代码
demo hello -n小明
demo hello -n 小明
demo hello -n=小明

布尔标志特殊(不需要值):

css 复制代码
demo hello --verbose
demo hello -v

组合短标志:-v -p 9090 可写成 -vp 9090

在 Run 函数中直接使用绑定的变量即可,也可以用 cmd.Flags().GetString("name")

4.3 本地标志 vs 持久标志 ------ 最重要的区别

css 复制代码
// 本地标志:只有 hello 命令自己能用
helloCmd.Flags().StringVar(&greeting, "greeting", "hi", "问候语")

// 持久标志:hello 及其所有子孙命令都能用
helloCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件路径")

// 最常见的做法:根命令定义全局持久标志
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "全局配置文件")

记忆法:

  • Flags() = 只给自己
  • PersistentFlags() = 给自己和所有后代
  • 父命令的持久标志,在子命令里显示为 Global Flags

4.4 必填标志

arduino 复制代码
helloCmd.Flags().StringVar(&apiKey, "api-key", "", "API 密钥(必填)")
helloCmd.MarkFlagRequired("api-key")            // 本地标志
helloCmd.MarkPersistentFlagRequired("token")    // 持久标志

未提供时执行报错:

swift 复制代码
Error: required flag(s) "api-key" not set

4.5 标志组约束

三个实用的组校验函数(定义在 flag_groups.go):

arduino 复制代码
// 三者必须同时出现,要么都不出现
cmd.MarkFlagsRequiredTogether("user", "password")

// 至少出现一个
cmd.MarkFlagsOneRequired("file", "url")

// 互斥,不能同时出现
cmd.MarkFlagsMutuallyExclusive("json", "yaml")

校验发生在 PreRun 之后、Run 之前(见源码 execute() 的调用顺序)。

4.6 常用变体标志

计数标志 (如 -vvv 表示级别 3):

csharp 复制代码
var verbosity int
cmd.Flags().CountVarP(&verbosity, "verbose", "v", "详细程度")

切片标志(可重复出现):

csharp 复制代码
var headers cmd.Flags().StringSlice("header", []string{}, "HTTP 头")
var tags cmd.Flags().StringArray("tag", []string{}, "标签")
// StringSlice: --tag a,b 解析为两个值;StringArray: 每次出现算一个值

五:位置参数校验

Args 字段控制命令接受多少个非标志参数。所有校验器定义在 args.go:

校验器 含义
cobra.NoArgs 不接受任何位置参数
cobra.ArbitraryArgs 任意参数
cobra.MinimumNArgs(n) 至少 n 个
cobra.MaximumNArgs(n) 至多 n 个
cobra.ExactArgs(n) 恰好 n 个
cobra.RangeArgs(min, max) 区间 min, max
cobra.OnlyValidArgs 必须在 ValidArgs 列表内
cobra.NoDuplicateArgs 不允许重复参数
cobra.MatchAll(...) 组合多个校验器

示例:

go 复制代码
var deleteCmd = &cobra.Command{
    Use:   "delete [名称...]",
    Short: "删除资源",
    Args:  cobra.MinimumNArgs(1), // 至少要给一个名称
    Run: func(cmd *cobra.Command, args []string) {
        for _, name := range args {
            fmt.Println("删除:", name)
        }
    },
}

组合校验:

vbnet 复制代码
Args: cobra.MatchAll(
    cobra.ExactArgs(2),
    cobra.OnlyValidArgs,
),
ValidArgs: []string{"alice", "bob"},

自定义校验器 ------ Args 本质是一个函数类型:

go 复制代码
Args: func(cmd *cobra.Command, args []string) error {
    for _, a := range args {
        if _, err := strconv.Atoi(a); err != nil {
            return fmt.Errorf("%q 不是数字", a)
        }
    }
    return nil
},

提示:不设置 Args 时,根命令若有子命令,传入未知子命令会报 "unknown command" 并附带建议。


六:命令生命周期钩子

一个命令完整执行时,各钩子的触发顺序(牢记这个顺序):

scss 复制代码
PersistentPreRun   (从父命令继承,最先执行;默认只找最近一个)
PreRun             (仅当前命令)
Run / RunE         (实际工作)
PostRun            (仅当前命令)
PersistentPostRun  (从父命令继承,最后执行)

典型用途:

  • PersistentPreRun:读取配置文件、初始化日志(定义在根命令上,所有子命令共享)
  • PreRun:本命令的前置检查
  • PostRun:清理临时文件
  • PersistentPostRun:关闭数据库连接

每个钩子都有 E 后缀版本(RunEPreRunE......),返回 error,由 Execute() 统一上报。同一个位置 E 版本和非 E 版本同时定义时,只有 E 版本生效

go 复制代码
var rootCmd = &cobra.Command{
    Use: "demo",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("[根] PersistentPreRun: 初始化日志")
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("[根] PersistentPostRun: 收尾")
    },
}

var helloCmd = &cobra.Command{
    Use: "hello",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("[hello] PersistentPreRun: 子命令覆盖了根命令的钩子")
    },
    PreRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("[hello] PreRun")
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("[hello] Run")
    },
    PostRun: func(cmd *cobra.Command, args []string) {
        fmt.Println("[hello] PostRun")
    },
}
// 执行 demo hello 输出:
// [hello] PersistentPreRun ...
// [hello] PreRun
// [hello] Run
// [hello] PostRun
// [根] PersistentPostRun ...

另外两个全局钩子(cobra.go):

  • cobra.OnInitialize(f1, f2):注册的函数在任何命令的 Run 之前执行(官方例子中用来读取配置)
  • cobra.OnFinalize(f):在所有 Run 结束后执行

如果想让父命令链上的每一级 PersistentPreRun 都执行(默认只执行最近的一个),设置全局变量:

ini 复制代码
cobra.EnableTraverseRunHooks = true

七:帮助与使用信息

7.1 自动获得的东西

●-h/--help:自动注入(源码InitDefaultHelpFlag)

●--version:设置了Version字段后自动注入(源码InitDefaultVersionFlag)

●help command:有子命令时自动注入(源码InitDefaultHelpCmd)

7.2 Short / Long / Example 的展示位置

kotlin 复制代码
&cobra.Command{
    Use:     "serve",
    Short:   "启动 HTTP 服务",     // 父命令 help 的命令列表里
    Long:    "serve 会启动一个......", // demo serve --help 的开头
    Example: `  demo serve --port 9090
  demo serve --tls`,                          // --help 中的 Examples 段
}

7.3 自定义帮助模板

css 复制代码
rootCmd.SetHelpTemplate(`{{.Long}}

用法: {{.UseLine}}
`)

或完全自定义帮助函数:

go 复制代码
rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
    fmt.Printf("这是 %s 的自定义帮助\n", cmd.Name())
})

7.4 自定义使用信息/版本模板

go 复制代码
rootCmd.SetUsageTemplate("...")
rootCmd.SetVersionTemplate(`{{.DisplayName}} 版本 {{.Version}}
`)

模板基于 Go text/template,可用 cobra.AddTemplateFunc 注入自定义函数。模板会沿命令树向上继承:子命令没设置就用父命令的。

7.5 命令输出流

scss 复制代码
cmd.Print/Println/Printf   // 输出到 Out(默认 stdout)
cmd.PrintErr/PrintErrln/PrintErrf // 输出到 Err(默认 stderr)
cmd.OutOrStdout() io.Writer      // 获取输出流,供 fmt.Fprintf 使用
cmd.ErrOrStderr() io.Writer
cmd.InOrStdin() io.Reader

测试或嵌入时可以替换:

scss 复制代码
cmd.SetOut(&buf)
cmd.SetErr(&buf)
cmd.SetIn(strings.NewReader("输入内容"))

八:错误处理

8.1 推荐范式:使用 RunE

go 复制代码
var fetchCmd = &cobra.Command{
    Use: "fetch [URL]",
    Args: cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        resp, err := http.Get(args[0])
        if err != nil {
            return fmt.Errorf("请求失败: %w", err)
        }
        defer resp.Body.Close()
        return nil
    },
}

错误会一路冒泡到 Execute()。cobra 默认已经:

  1. 打印 Error: xxx 到 stderr
  1. 打印使用信息

8.2 SilenceUsage / SilenceErrors

Run 内部的业务错误往往不需要再显示 usage,常见做法:

css 复制代码
Run: func(cmd *cobra.Command, args []string) {
    if err := doWork(); err != nil {
        // 静默 usage 输出,只保留 Error: 行,再以非 0 退出
        cmd.SilenceUsage = true
        cmd.PrintErrln("Error:", err)
        os.Exit(1)
    }
},

说明:

  • SilenceUsage = true:出错时不打印 usage(在 Run 里动态设置正是官方推荐用法)
  • SilenceErrors = true:连 Error: 行也不打印,完全自己处理
  • 根命令设置后对所有子命令生效

8.3 自定义 flag 解析错误处理

go 复制代码
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
    return fmt.Errorf("参数解析出错(命令 %s): %w", cmd.Name(), err)
})

8.4 便捷函数

lua 复制代码
cobra.CheckErr(err) // err 非 nil 时打印 "Error: err" 并 os.Exit(1)

8.5 退出码

Execute() 返回 error 后,记得在 main 里处理:

go 复制代码
func Execute() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

flag.ErrHelp 是哨兵错误:当用户请求 --help 或命令不可执行时,execute 返回它,ExecuteC 捕获后显示帮助且不算失败(返回 nil)。


九:Shell 自动补全

Cobra 内置为四种 shell 生成补全:bashzshfishpowershell

9.1 生成补全脚本

只要程序有子命令,completion 命令会自动出现:

shell 复制代码
$ demo completion bash
# ...输出的脚本 source 后即可补全

各 shell 的启用方式:

bash 复制代码
# bash(新版支持描述)
demo completion bash > /usr/local/etc/bash_completion.d/demo

# zsh
demo completion zsh > "${fpath[1]}/_demo"

# fish
demo completion fish | source

# PowerShell
demo completion powershell | Out-String | Out-File -Encoding utf8 demo.ps1

9.2 静态补全:ValidArgs

go 复制代码
var getCmd = &cobra.Command{
    Use:       "get [资源]",
    ValidArgs: []string{"pods", "services", "deployments"},
    Args:      cobra.OnlyValidArgs,
    Run: func(cmd *cobra.Command, args []string) { ... },
}

输入 demo get <TAB> 时会补全这三个词。格式 "值\t描述" 可以带描述。

9.3 动态补全:ValidArgsFunction

go 复制代码
var helloCmd = &cobra.Command{
    Use: "hello [名字]",
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) (
        []string, cobra.ShellCompDirective) {
        if len(args) > 0 {
            return nil, cobra.ShellCompDirectiveNoFileComp
        }
        return []string{"alice\t爱丽丝", "bob\t鲍勃"}, cobra.ShellCompDirectiveNoFileComp
    },
}
  • toComplete 是当前正在输入的词,cobra 会自动做前缀过滤(此处简单返回全部即可)
  • ShellCompDirectiveNoFileComp 表示"别再 fallback 到文件补全"

常用指令位:

指令 含义
ShellCompDirectiveNoFileComp 禁止文件补全
ShellCompDirectiveNoSpace 补全后不加空格
ShellCompDirectiveFilterDirs 只补全目录
ShellCompDirectiveKeepOrder 保持给定顺序

9.4 标志值补全

go 复制代码
helloCmd.RegisterFlagCompletionFunc("name",
    func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
        return []string{"alice", "bob"}, cobra.ShellCompDirectiveNoFileComp
    })

demo hello --name <TAB> 就会补全 alice/bob。


十:进阶技巧

10.1 智能建议(Levenshtein)

输错命令时 cobra 自动建议:

typescript 复制代码
$ demo hleo
Error: unknown command "hleo" for "demo"

Did you mean this?
    hello

相关配置:

  • SuggestionsMinimumDistance:编辑距离阈值(默认 2)
  • DisableSuggestions = true:关闭建议
  • SuggestFor:为命令声明额外的"易错拼写"

10.2 命令别名与隐藏

c 复制代码
&cobra.Command{
    Use:     "serve",
    Aliases: []string{"s", "server", "start"}, // demo s 等价于 demo serve
}

&cobra.Command{
    Use:    "legacy",
    Hidden: true, // 不出现在 help 中,但仍然可用
}

10.3 弃用提示

arduino 复制代码
&cobra.Command{
    Use:        "old",
    Deprecated: "请改用 new 命令", // 执行时会打印 deprecation 警告
}

10.4 自定义输入参数(用于测试)

c 复制代码
rootCmd.SetArgs([]string{"hello", "小明"})
rootCmd.Execute()

Execute() 默认读 os.Args[1:],可覆盖:

这是给 CLI 写单元测试的标准姿势(配合 SetOut(&buf) 断言输出)。

10.5 Context 传递

go 复制代码
if err := rootCmd.ExecuteContext(ctx); err != nil { ... }

// Run 内部:
Run: func(cmd *cobra.Command, args []string) {
    ctx := cmd.Context() // 拿到信号处理的 ctx
}

典型场景:在 main 里创建带 signal.NotifyContext 的 ctx,收到 Ctrl-C 时 Run 内部的 HTTP 服务优雅退出。

10.6 生成文档

php 复制代码
import "github.com/spf13/cobra/doc"

doc.GenManTree(rootCmd, &doc.GenManHeader{Title: "DEMO", Section: "1"}, "./man")
doc.GenMarkdownTree(rootCmd, "./docs")
doc.GenYamlTree(rootCmd, "./docs")

doc/ 子包提供文档生成:

10.7 前缀匹配与大小写

包级开关(慎用):

ini 复制代码
cobra.EnablePrefixMatching = true     // demo ser 匹配 serve(有歧义时不行)
cobra.EnableCaseInsensitive = true    // 命令名忽略大小写
cobra.EnableCommandSorting = false    // help 中不按名称排序子命令

10.8 与 Viper 集成配置绑定

go 复制代码
import "github.com/spf13/viper"

func init() {
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "配置文件")
    // 标志变化时同步到 viper
    viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
    viper.BindPFlags(rootCmd.Flags())
}

之后 viper.GetString("verbose") 的取值优先级:命令行 > 环境变量 > 配置文件 > 默认值。

10.9 Active Help(动态帮助提示)

arduino 复制代码
cobra.AppendActiveHelp(completions, "当前没有可用的集群")

补全时向用户显示提示信息(如"请先选择集群"):

第十一章:完整实战示例

go 复制代码
todo/
├── main.go
├── cmd/
│   ├── root.go
│   ├── add.go
│   ├── list.go
│   └── done.go
└── go.mod

综合运用前面所有知识点,实现一个 todo 待办事项工具:

main.go:

go 复制代码
package main

import "todo/cmd"

func main() {
    cmd.Execute()
}

cmd/root.go:

go 复制代码
package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var (
    dataFile string
    verbose  bool
)

var rootCmd = &cobra.Command{
    Use:   "todo",
    Short: "一个简单的待办事项工具",
    Long:  "todo ------ 演示 cobra 各项功能的完整示例。",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        if verbose {
            fmt.Println("[debug] 数据文件:", dataFile)
        }
    },
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

func init() {
    rootCmd.PersistentFlags().StringVar(&dataFile, "data", "todo.json", "数据文件路径")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "输出调试信息")
}

cmd/add.go:

go 复制代码
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var addPriority int

var addCmd = &cobra.Command{
    Use:     "add [内容]",
    Aliases: []string{"a"},
    Short:   "添加一条待办",
    Args:    cobra.MinimumNArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        content := strings.Join(args, " ")
        if addPriority < 1 || addPriority > 5 {
            return fmt.Errorf("--priority 必须在 1~5 之间")
        }
        fmt.Printf("已添加(优先级 %d): %s\n", addPriority, content)
        return nil // 实际项目中这里写入 dataFile
    },
}

func init() {
    addCmd.Flags().IntVarP(&addPriority, "priority", "p", 3, "优先级 1~5")
    addCmd.MarkFlagRequired("priority") // 演示必填
    rootCmd.AddCommand(addCmd)
}

cmd/list.go:

go 复制代码
package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var showDone bool

var listCmd = &cobra.Command{
    Use:   "list",
    Short: "列出所有待办",
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) (
        []string, cobra.ShellCompDirective) {
        return nil, cobra.ShellCompDirectiveNoFileComp
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("(这里应从 dataFile 读取并打印)")
        if showDone {
            fmt.Println("包含已完成项")
        }
    },
}

func init() {
    listCmd.Flags().BoolVar(&showDone, "done", false, "同时显示已完成")
    rootCmd.AddCommand(listCmd)
}

体验一下:

shell 复制代码
$ todo add --help
$ todo add "学习 cobra" -p 1
$ todo -v list --done
$ todo a "快速加一条" -p 2        # 别名生效
$ todo lis                       # 触发建议: Did you mean "list"?

附录:常见问题

Q1:为什么我的 --help 没显示某个标志?

本地标志显示在 Flags 段,父命令的持久标志显示在 Global Flags 段。检查你用的是 Flags() 还是 PersistentFlags(),以及是否定义在父命令上。

Q2: Error: unknown command 但我明明只传了参数?

根命令有子命令且未设置 Args 时,第一个位置参数会被当成子命令名。给根命令设置 Args: cobra.ArbitraryArgs,或确保子命令正确 AddCommand 了。

Q3:怎么在 Run 里拿到 flag 值?

最简单:Flags().XxxVar 绑定的包级变量直接读;或 cmd.Flags().GetString("name")

Q4:PreRun 和 OnInitialize 有什么区别?

OnInitialize 注册的函数在所有命令 Run 前执行,常用于加载配置;PersistentPreRun 是命令树上的钩子,子命令可覆盖,且能拿到 cmd/args

Q5:怎么隐藏自动生成的 completion 命令?

Q6:Windows 下双击运行 CLI 弹出提示?

这是 cobra 的"捕鼠夹"机制(防止用户从资源管理器误启动)。设置 cobra.MousetrapHelpText = "" 可关闭。

Q7:Execute 报错后 usage 总是打出来,很吵?

在 RunE 返回前设置 cmd.SilenceUsage = true,或干脆在根命令上设置。

相关推荐
用户345138101342 小时前
搭建一个springboot项目并整合其他中间件(更新中)
后端
用户7713970207062 小时前
ASP.NET Core Identity 从入门到实战:常见问题与解决方案
后端
用户298698530142 小时前
Python 实现文本文件与 Word 文档互转的两种方法
后端·python·api
Zane19942 小时前
明明在赋值前读取,为什么还会报 UnboundLocalError?global 与 nonlocal 深挖
后端·python
YHL2 小时前
🎯 Danci —— 用 AI 驱动开发一个全栈英语单词学习平台
前端·后端
步行cgn2 小时前
Spring 的核心特点与设计哲学
后端
掘金者阿豪2 小时前
数据库迁移最麻烦的,不是迁,而是动手之前心里没底
后端
中趴菜2 小时前
HTML 双重转义排查实战指南
后端
AskHarries2 小时前
花了 500 大洋买下 bbs.ss,我决定做一个真正属于出海人的论坛
后端