Cobra 是一个 Golang 包,它提供了简单的接口来创建命令行程序。同时,Cobra 也是一个应用程序,用来生成应用框架,从而开发以 Cobra 为基础的应用。
主要功能
- 简易的子命令行模式,如 app server, app fetch 等等
- 完全兼容 posix 命令行模式
- 嵌套子命令 subcommand
- 支持全局,局部,串联 flags
- 使用 cobra 很容易的生成应用程序和命令,使用 cobra create appname 和 cobra add cmdname
- 如果命令输入错误,将提供智能建议,如 app srver,将提示 srver 没有,是不是 app server
- 自动生成 commands 和 flags 的帮助信息
- 自动生成详细的 help 信息,如 app help
- 自动识别帮助 flag -h,–help
- 自动生成应用程序在 bash 下命令自动完成功能
- 自动生成应用程序的 man 手册
- 命令行别名
- 自定义 help 和 usage 信息
- 可选的与 viper apps 的紧密集成
cobra 中的主要概念
- commands 行为
- args 命令行参数(或称为位置参数)
- flags 对行为的改变(即命令行选项)
执行命令行程序时的一般格式为: APPNAME COMMAND ARG --FLAG
创建 cobra 应用
获取最新版本
1 | $ go get -u github.com/spf13/cobra@latest |
安装 cobra-cli
1 | $ go install github.com/spf13/cobra-cli@latest |
创建
1 | $ cd /pathto/mysrc |
执行后,该目录下生成的结构如下:
1 | ▾ demo |
使用 cobra 程序生成命令代码
除了生成应用程序框架,还可以生成子命令的代码文件。添加自命令 mysub1
1 | $ cd /pathto/mysrc |
为命令添加具体的功能
打开文件 cmd/root.go
,找到变量 rootCmd 的初始化过程并为之设置 Run 方法:
1 | Run: func(cmd *cobra.Command, args []string) { |
创建一个 version Command 用来输出当前的软件版本。先在 cmd 目录下添加 version.go 文件,编辑文件的内容如下:
1 | package cmd |
为 Command 添加选项(flags)
选项(flags)用来控制 Command 的具体行为。根据选项的作用范围,可以把选项分为两类:
- persistent
- local
对于 persistent 类型的选项,既可以设置给该 Command,又可以设置给该 Command 的子 Command。对于一些全局性的选项,比较适合设置为 persistent 类型,比如控制输出的 verbose 选项:
1 | var Verbose bool |
local 类型的选项只能设置给指定的 Command,比如下面定义的 source 选项:
1 | var Source string |
该选项不能指定给 rootCmd 之外的其它 Command。
默认情况下的选项都是可选的,但一些用例要求用户必须设置某些选项,这种情况 cobra 也是支持的,通过 Command 的 MarkFlagRequired 方法标记该选项即可:
1 | var Name string |
命令行参数(arguments)
命令行参数(arguments)与命令行选项的区别(flags/options)。以常见的 ls 命令来说,其命令行的格式为:ls [OPTION]... [FILE]…
其中的 OPTION 对应本文中介绍的 flags,以 -
或 --
开头;而 FILE 则被称为参数(arguments)或位置参数。一般的规则是参数在所有选项的后面,上面的 … 表示可以指定多个选项和多个参数。
cobra 默认提供了一些验证方法:
- NoArgs - 如果存在任何位置参数,该命令将报错
- ArbitraryArgs - 该命令会接受任何位置参数
- OnlyValidArgs - 如果有任何位置参数不在命令的 ValidArgs 字段中,该命令将报错
- MinimumNArgs(int) - 至少要有 N 个位置参数,否则报错
- MaximumNArgs(int) - 如果位置参数超过 N 个将报错
- ExactArgs(int) - 必须有 N 个位置参数,否则报错
- ExactValidArgs(int) 必须有 N 个位置参数,且都在命令的 ValidArgs 字段中,否则报错
- RangeArgs(min, max) - 如果位置参数的个数不在区间 min 和 max 之中,报错
帮助信息(help command)
cobra 会自动添加 --help(-h)
选项,同时还自动添加了 help 子命,默认效果和使用 –help 选项相同。如果为 help 命令传递其它命令作为参数,则会显示对应命令的帮助信息。也可以自定义 help 的处理
1 | cmd.SetHelpCommand(cmd *Command) |
提示信息(usage message)
提示信息和帮助信息很相似,只不过它是在你输入了非法的参数、选项或命令时才出现的。也可以自定义提示信息:
1 | cmd.SetUsageFunc(f func(*Command) error) |
在 Commnad 执行前后执行额外的操作
Command 执行的操作是通过 Command.Run 方法实现的,为了支持我们在 Run 方法执行的前后执行一些其它的操作,Command 还提供了额外的几个方法,它们的执行顺序如下:
- PersistentPreRun
- PreRun
- Run
- PostRun
- PersistentPostRun
1 | var rootCmd = &cobra.Command{ |
代码解析
Command 结构体
Command 结构体是 cobra 抽象出来的核心概念,它的实例表示一个命令或者是一个命令的子命令。下面的代码仅展示 Command 结构体中一些比较重要的字段:
1 | type Command struct { |
执行命令的逻辑
cobra 包启动程序执行的代码一般为:
1 | cmd.Execute() |
Execute() 函数会调用我们定义的 rootCmd(Command 的一个实例)的 Execute() 方法。
在 Command 的 Execute() 方法中又调用了 Command 的 ExecuteC() 方法,我们可以通过下面的调用堆栈看到执行命令逻辑的调用过程:
1 | cmd.Execute() -> // main.go |
c.Run() 方法即用户为命令(Command) 设置的执行逻辑。
解析命令行子命令
ExecuteC() 方法中,在执行 execute() 方法前,需要先通过 Find() 方法解析命令行上的子命令:
1 | cmd, flags, err = c.Find(args) |
比如我们执行下面的命令:
1 | $ ./myApp mycmd1 |
解析出的 cmd 就是 imamycmd1ge 子命令,接下来就是执行 mycmd1 子命令的执行逻辑。
Find() 方法的逻辑如下:
1 | $ ./myApp help mycmd1 |
这里的 myApp 对应代码中的 rootCmd,Find() 方法中定义了一个名称为 innerfind 的函数,innerfind 从参数中解析出下一个名称,这里是 help,然后从 rootCmd 开始查找解析出的名称 help 是不是当前命令的子命令,如果 help 是 rootCmd 的子命令,继续查找。接下来查找名称 mycmd1,发现 mycmd1 不是 help 的子命令,innerfind 函数就返回 help 命令。execute() 方法中就执行这个找到的 help 子命令。
为根命令添加 help 子命令
在执行 ExecuteC() 方法时,cobra 会为根命令添加一个 help 子命令,这个子命令主要用来提供子命令的帮助信息。因为任何一个程序都需要提供输出帮助信息的方式,所以 cobra 就为它实现了一套默认的逻辑。help 子命令是通过 InitDefaultHelpCmd() 方法添加的,其实现代码如下:
1 | // InitDefaultHelpCmd adds default help command to c. |
如果没有找到用户指定的子命令,就输出错误信息,并调用根命令的 Usage() 方法:
1 | c.Printf("Unknown help topic %#q\n", args) |
cobra 默认提供的 usage 模板如下:
1 | `Usage:{{if .Runnable}} |
如果找到用户指定的子命令,就为子命令添加默认的 help flag,并执行其 Help() 方法:
1 | cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown |
示例:
通过 cobra 实现了一个命令行程序 myApp,它有一个子命令 image,image 也有一个子命令 times。执行下面的命令:
1 | $ ./myApp help mycmd1 |
在 help 命令的 Run 方法中,c 为 help 命令, args 为 mycmd1。结果就是通过 help 查看 mycmd1 命令的帮助文档。如果 mycmd1 后面还有其他的子命令,比如:
1 | $ ./myApp help mycmd1 mysub1 |
则 c.Root().Find(args) 逻辑会找出子命令 mysub1(此时 args 为 mycmd1 mysub1),最终由 help 查看 mysub1 命令的帮助文档。
注意:help 信息中包含 usage 信息。
为命令添加 help flag
除了在 InitDefaultHelpCmd() 方法中会调用 InitDefaultHelpFlag() 方法,在 execute() 方法中执行命令逻辑前也会调用 InitDefaultHelpFlag() 方法为命令添加默认的 help flag,
1 | c.InitDefaultHelpFlag() |
下面是 InitDefaultHelpFlag() 方法的实现:
1 | // InitDefaultHelpFlag adds default help flag to c. |
这让我们不必为命令添加 help flag 就可以直接使用
输出 help 信息
不管是 help 命令还是 help falg,最后都是通过 HelpFunc() 方法来获得输出 help 信息的逻辑:
1 | // HelpFunc returns either the function set by SetHelpFunc for this command |
如果我们没有指定自定义的逻辑,就找父命令的,再没有就用 cobra 的默认逻辑。cobra 默认设置的帮助模板如下(包含 usage):
1 | `{{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}} |
参考资料: