很多 Go 的开源软件都使用 Cobra 库来构建自己的命令行(CLI)应用程序,这些开源软件包括 Kubernetes
、Hugo
等等。这篇文章会对 Cobra 库的使用做一个基本介绍。
Cobra 简介
Cobra 库提供了简单的接口来帮助 Gopher 创建强大的、现代化的命令行应用程序(类似于 git 命令)。Cobra 提供了以下特性:
- 简易的基于子命令的 CLI:例如
app server
、app fetech
- 兼容 POSIX 的 flags(短、长版本)
- 嵌套的子命令
- 全局、局部、级联 flags
- 智能建议
- 自动生成帮助
- 子命令帮助分组
- 自动识别
-h
、--help
帮助标志 - 自动生成 shell 补全
- 自动生成 manpages
- 命令别名
- 自定义帮助、usage 等等
- 和
viper
无缝集成
Cobra 建立在 commands
、arguments
、flags
这三个概念之上:
commands
代表动作,应用程序支持的每一种交互应该包含在一个Command
中。Command 可以包含子命令arguments
代表事物flags
代表对这些动作的装饰,用来修改 command 的行为。Cobra 支持 POSIX 兼容的 flags,也支持 Go 标准 flag 库。Cobra 的flags
功能是通过 pflag library 来实现的。pflag
库完全兼容flag
标准库的接口,同时又增加了对 POSIX 的兼容
Cobra 认为使用命令行程序时应该像阅读句子一样自然,应该遵循 APPNAME VERB NOUN --ADJECTIVE
或 APPNAME COMMAND ARG --FLAG
这样的模式。
使用方法
当使用 Cobra
库来构建命令行应用程序时,同时按照如下方式组织相关代码:
1 | ▾ appName/ |
main.go
通常非常简单,它只需要初始化 Cobra:
1 | package main |
Cobra 还提供了一个 Cobra-CLI
程序,来帮助你自动生成基于 Cobra
库的命令行程序,无需自己为每个命令编写对应的代码:
例如想创建如下的命令行接口:
1 | app serve |
直接按照如下方式即可生成:
1 | cobra-cli add serve |
Cobra-CLI
只是帮助我们快速创建出 CLI 程序的框架,我们也可以手动完成这一过程。所以接下来将完整地介绍 Cobra 库的使用。
创建 rootCmd
Cobra 不需要任何特殊的构造器,而是直接创建命令即可。首先需要创建 rootCmd,这个通常定义在 app/cmd/root.go
文件中。
1 | var rootCmd = &cobra.Command{ |
同时我们可以在 init()
函数定义额外的 flags 以及处理配置等。
创建 main.go
为了让 main 函数执行该 root 命令,通常会按照方式调用 cmd
包中的 Execute()
。如上所示, Execute()
则通常就是简单地调用 rootCmd.Execute()
。
1 | package main |
创建额外的命令
可以继续定义其他命令,这些命令通常放在 cmd/
目录下的独立文件中。例如想创建一个 version
子命令,可以增加一个 cmd/version.go
文件:
1 | package cmd |
组织子命令
要在某个命令中增加子命令,可以通过 AddCommand()
来实现。对于一些大型应用程序而言,每个子命令可能定义在它自己的 go 包中。推荐方法是父命令使用 AddCommand()
添加其最直接的子命令。例如,考虑以下目录结构:
1 | ├── cmd |
那么:
root.go
的init
函数将sub1.go
中定义的命令添加到 root 命令中sub1.go
的init
函数将sub2.go
中定义的命令添加到 sub1 命令中sub2.go
中的init
函数将leafA.go
和leafB.go
中的命令添加到 sub2 命令中
返回和处理错误
如果想给命令的调用方返回错误,可以使用 RunE
:
1 | package cmd |
这样函数执行时返回的错误可以被捕获。
使用 flags
flags 可以控制命令的行为,接下来介绍如何使用 flags。
首先需要给命令增加 flags
,由于 flags 的定义和使用通常在不同的位置,所以会在合适的作用域内定义相应的变量,来记录 flag 的设置:
1 | var Verbose bool |
有两种方式来添加 flag:
Persistent Flags
:意味这该 flag 对该命令及其所有子命令都有效
1 | rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") |
Local Flags
:只对指定的命令生效
1 | localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") |
默认,Cobra 只会解析目标命令上的 local flags,而其 parent 命令上的 local flags 都会被忽略。通过开启 Command.TraverseChildren
,Cobra 在执行目标命令前会解析每一个命令上的 local flags。
在 viper 中也可以使用 flags,详细参考 viper 文档。
flag 默认都是可选的,如果想把某个 flag 设置为必须得,可以通过如下方式实现:
1 | rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)") |
Flag 分组
如果有不同的 flag,而有些 flag 必须一起提供,可以这样实现:
1 | rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)") |
如果某些 flag 是互斥的,可以这样设置:
1 | rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON") |
如果一组 flag 必须使用其中的某一个,可以使用 MarkFlagsOneRequired
。如果结合 MarkFlagsMutuallyExclusive
使用则表示必须只能使用某一个 flag:
1 | rootCmd.Flags().BoolVar(&ofJson, "json", false, "Output in JSON") |
位置和自定义参数
可以通过 Command
的 Args
字段来对位置参数进行校验,Cobra 内建了以下校验器:
- 参数个数校验:NoArgs、ArbitraryArgs、MinimumNArgs(int)、MaximumNArgs(int)、ExactArgs(int)、RangeArgs(min, max)
- 参数内容校验:OnlyValidArgs(如果某个位置参数不在
Command
的ValidArgs
中,则报告错误)
MatchAll(pargs ...PositionalArgs)
可以将多个检查进行 与
操作。
可以自定义参数校验器,其函数原型需要满足:func(cmd *cobra.Command, args []string) error
。例如:
1 | var cmd = &cobra.Command{ |
帮助命令
Cobra 会自动为应用程序(只要有子命令)添加 help 子命令,可以通过 app help
的方式运行 help
子命令。而且 help
命令也支持将其他子命令作为参数输入,以获取某个子命令的帮助信息。另外,每个命令自动都添加了 -h
flag,用于获取某个的帮助信息。
1 | # ./app help |
Cobra 支持在帮助输出里对命令进行分组,通过在父命令中使用 AddGroup
可以显式定义每个分组,之后子命令可以通过 GroupID
来加入某个分组。如果是使用生成的 help
或 completion
命令,可以在 root 命令中使用 SetHelpCommandGroupId
、SetCompletionCommandGroupId
来设置它们的 group id。
也可以自己提供 Help 命令,或者使用默认的 Help
命令,但是提供新的帮助信息模版:
1 | cmd.SetHelpCommand(cmd *Command) |
Usage 信息
当用户提供了无效的 flag 或命令时,Cobra 会给用户展示 usage
消息。同样可以提供自己的 usage
函数或者模版:
1 | cmd.SetUsageFunc(f func(*Command) error) |
Version 标志
如果 root 命令下有 Version
字段,那么 cobra 会自动添加一个顶级的 --version
flag。当使用 --version
运行程序时,cobra 会在标准输出中输出版本信息。可以通过 cmd.SetVersionTemplate(s string)
设置自定义的版本信息模版。
错误消息前缀
当收到错误消息时,Cobra 会打印错误消息。默认的错误消息时 Error: <error contents>
。可以通过 cmd.SetErrPrefix(s string)
替换默认的前缀 Error:
。
PreRun and PostRun Hooks
可以在运行命令的 Run
函数之前或之后运行指定的函数。PersistentPreRun
和 PreRun
在 Run
函数之前运行;PersistentPostRun
和 PostRun
在 Run
函数之后运行。Persistent*Run
函数会被子命令继承(如果它们没有定义自己的 Persistent*Run
函数)。所以函数运行的顺序如下:
- PersistentPreRun
- PreRun
- Run
- PostRun
- PersistentPostRun
未知命令时的行为
当 unknown command
错误发生时,Cobra 会打印自动地建议消息:
1 | # ./app ec |
建议信息是基于当前已经存在的子命令以及 Levenshtein distance
自动生成的。可以通过 command.DisableSuggestions = true
关闭自动建议,或者 command.SuggestionsMinimumDistance = 1
调整生成的帮助信息。也可以通过 SuggestFor
属性来为指定的字符串设置推荐的命令。
生成帮助文档、 shell 补全、Active Help
Cobra 也支持根据子命令、flag 等信息生成文档,可以参考 docs generation documentation
[https://github.com/spf13/cobra/blob/main/site/content/docgen/_index.md].
Cobra 也支持为各种 shell 生成 shell 补全文件,可以参考 Shell Completions
[https://github.com/spf13/cobra/blob/main/site/content/completions/_index.md]。
Cobra 也使用 shell 的补全系统来提供 Active Help,具体参考 Active Help
[https://github.com/spf13/cobra/blob/main/site/content/active_help.md]。
简单示例
最后是一个简单的实例:
1 | package main |