概念
cobra 构建中,有三块基石头:命令、参数和标志。要使用 Cobra 编写一个命令行程序,首先要明确这三个概念:
- 命令(COMMAND):命令表示要执行的操作。
- 参数(ARG):是命令的参数,一般用来表示操作的对象。
- 标志(FLAG):是命令的修饰,可以调整操作的行为。
一个好的命令行程序在使用时读起来像句子,用户会自然的理解并知道如何使用该程序。比如:
git log master --oneline --graph
其中:
log 是命令,表明执行的操作是查看日志;
master 是参数, 指明log操作的对象是master分支;
--oneline是标志,用于限定log的行为,以简洁单行的方式显示;
--graph也是标志,用于限定log的行为,以图形化方式显示提交和分支关系。
要编写一个好的命令行程序,通常遵循的模式是:
APPNAME VERB NOUN --ADJECTIVE
或 APPNAME COMMAND ARG --FLAG
在这里 VERB
代表动词,NOUN
代表名词,ADJECTIVE
代表形容词。
另一方面,在实际应用中,如果一个命令有多个参数,为了方便使用和控制,通常会用具名参数的形式,即参数选项化:
ssh -p 1000 smbody@192.168.1.1
hull create “name” “email” “class” --> hull create "name" -E "email" -c "class"
快速开始
要使用 cobra 创建命令行程序,需要先通过如下命令进行安装:
$ go get -u github.com/spf13/cobra/cobra
安装好后,就可以像其他 Go 语言库一样导入 cobra 包并使用了。
import "github.com/spf13/cobra"
创建一个命令
以官网例子,假设我们要创建的命令行程序叫作 hugo,可以编写如下代码创建一个命令:
hugo/cmd/root.go
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at gohugo.io`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("run hugo...")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
cobra.Command
是一个结构体,代表一个命令,其各个属性含义如下:
Use
是命令的名称。
Short
代表当前命令的简短描述。
Long
表示当前命令的完整描述。
Run
属性是一个函数,当执行命令时会调用此函数。
rootCmd.Execute()
是命令的执行入口,其内部会解析 os.Args[1:]
参数列表(默认情况下是这样,也可以通过 Command.SetArgs
方法设置参数),然后遍历命令树,为命令找到合适的匹配项和对应的标志。
创建 main.go
按照编写 Go 程序的惯例,我们要为 hugo
程序编写一个 main.go
文件,作为程序的启动入口。
package main
import (
"hugo/cmd"
)
func main() {
cmd.Execute()
}
main.go
代码实现非常简单,只在 main
函数中调用了 cmd.Execute()
函数,来执行命令。
编译并运行命令
现在,我们就可以编译并运行这个命令行程序了。
go build -o hugo main.go
$ ./hugo --help
A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at gohugo.io
Usage:
hugo [flags]
Flags:
-h, --help help for hugo
命令与子命令
与定义 rootCmd
一样,我们可以使用 cobra.Command
定义其他命令,并通过 rootCmd.AddCommand()
方法将其添加为 rootCmd
的一个子命令。
hugo/cmd/version.go
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
现在重新编译并运行命令行程序。
$ ./hugo -h
A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at gohugo.io
Usage:
hugo [flags]
hugo [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
version Print the version number of Hugo
Flags:
-h, --help help for hugo
Use "hugo [command] --help" for more information about a command.
可以看见version命令已经被加进来了。运行version子命令:
$ ./hugo version
Hugo Static Site Generator v0.9 -- HEAD
当然,子命令可以继续添加子命令,从而实现多级命令嵌套。cobra 会自动进行命令树展开,并匹配对应的选项。
在设计多级命令时,通常按一下规则:
APPNAME GROUP... ACTION ARGs [FLAGS]
标志
标志配置和获取值
cobra.Command 提供了Flags() 接口及一系列选项配置方法,来为指定的命令设置选项。例如:
subCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
为subCmd 配置一个 --source/-s 选项,选项的值是 字符串类型,默认值是“”;help 说明为“Source directory to read from”。
cobra.Command 绑定的run函数, 原型为:
func(command *cobra.Command, args []string) {
}
通常,实现的run函数中,会从 command 中获取相关的选项内容,用于逻辑控制:
name, err := command.Flags().GetString("grep-name")
if err != nil {
ErrorExit("参数或选项解析错误:" + err.Error())
}
持久标志
如果一个标志是持久的
,则意味着该标志将可用于它所分配的命令以及该命令下的所有子命令。
对于全局标志,可以定义在根命令 rootCmd
上。
var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
这样,所有命令都可以继承和使用该标志。
本地标志
标志也可以是本地的
,这意味着它只适用于该指定命令。
var Source string
subCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
必选标志
默认情况下,标志是可选的。我们可以将其标记为必选,如果没有提供,则会报错。
var Region string
subCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
subCmd.MarkFlagRequired("region")
参数验证
在执行命令行程序时,我们可能需要对命令参数进行合法性验证,cobra.Command
的 Args
属性提供了此功能。
Args
属性类型为一个函数:func(cmd *Command, args []string) error
,可以用来验证参数。
Cobra 内置了以下验证函数:
NoArgs
:如果存在任何命令参数,该命令将报错。ArbitraryArgs
:该命令将接受任意参数。OnlyValidArgs
:如果有任何命令参数不在Command
的ValidArgs
字段中,该命令将报错。MinimumNArgs(int)
:如果没有至少 N 个命令参数,该命令将报错。MaximumNArgs(int)
:如果有超过 N 个命令参数,该命令将报错。ExactArgs(int)
:如果命令参数个数不为 N,该命令将报错。ExactValidArgs(int)
:如果命令参数个数不为 N,或者有任何命令参数不在Command
的ValidArgs
字段中,该命令将报错。RangeArgs(min, max)
:如果命令参数的数量不在预期的最小数量min
和最大数量max
之间,该命令将报错。- 内置验证函数用法如下:
var versionCmd = &cobra.Command{
Use: "version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
Args: cobra.MaximumNArgs(2), // 使用内置的验证函数,位置参数多于 2 个则报错
}
当然,我们也可以自定义验证函数:
var printCmd = &cobra.Command{
Use: "print [OPTIONS] [COMMANDS]",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("run print...")
// 命令行位置参数列表:例如执行 `hugo print a b c d` 将得到 [a b c d]
fmt.Printf("args: %v\n", args)
},
// 使用自定义验证函数
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires at least one arg")
}
if len(args) > 4 {
return errors.New("the number of args cannot exceed 4")
}
if args[0] != "a" {
return errors.New("first argument must be 'a'")
}
return nil
},
}
Hooks
在执行 Run
函数前后,我么可以执行一些钩子函数,其作用和执行顺序如下:
PersistentPreRun
:在PreRun
函数执行之前执行,对此命令的子命令同样生效。PreRun
:在Run
函数执行之前执行。Run
:执行命令时调用的函数,用来编写命令的业务逻辑。PostRun
:在Run
函数执行之后执行。PersistentPostRun
:在PostRun
函数执行之后执行,对此命令的子命令同样生效。
可以使用如下方式测试:
var rootCmd = &cobra.Command{
Use: "hugo",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hugo PersistentPreRun")
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hugo PreRun")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("run hugo...")
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hugo PostRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hugo PersistentPostRun")
},
}
通常,可以在root command 中,指定PresistentPreRun 方法,用于执行一些公共处理逻辑(比如,检查登录状态等),达到初始化或者配置的目的。
自定义usage 或help
定义自己的 Help 命令
如果你对 obra 自动生成的帮助命令不满意,我们可以自定义帮助命令或模板。
cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)
obra 提供了三个方法来实现自定义帮助命令,后两者也适用于任何子命令。
默认情况下,我们可以使用 hugo help command
语法查看子命令的帮助信息,也可以使用 hugo command -h/--help
查看。
使用 help
命令查看帮助信息:
$ ./hugo help version
hugo PersistentPreRunE
All software has versions. This is Hugo's
Usage:
hugo version [flags]
Flags:
-h, --help help for version
Global Flags:
--author string Author name for copyright attribution (default "YOUR NAME")
-v, --verbose verbose output
hugo PersistentPostRun
使用 -h/--help
查看帮助信息:
$ ./hugo version -h
All software has versions. This is Hugo's
Usage:
hugo version [flags]
Flags:
-h, --help help for version
Global Flags:
--author string Author name for copyright attribution (default "YOUR NAME")
-v, --verbose verbose output
对比发现,使用 help [cmd]时,会执行命令的Hook 函数,而使用 [cmd] help 不会。
定义自己的 Usage Message
当用户提供无效标志或无效命令时,cobra 通过向用户显示 Usage
来提示用户如何正确的使用命令。
与 help
信息一样,我们也可以进行自定义。cobra 提供了如下两个方法,来控制输出:
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)
未知命令建议
在我们使用 git
命令时,有一个非常好用的功能,能够对用户输错的未知命令智能提示。
$ git statu
git: 'statu' is not a git command. See 'git --help'.
The most similar commands are
status
stage
stash
这个功能非常实用,幸运的是,cobra 自带了此功能。
如果你想彻底关闭此功能,可以使用如下设置:
command.DisableSuggestions = true
或者使用如下设置调整字符串匹配的最小距离:
command.SuggestionsMinimumDistance = 1
SuggestionsMinimumDistance
是一个正整数,表示输错的命令与正确的命令最多有几个不匹配的字符(最小距离),才会给出建议。如当值为 1
时,用户输入 hugo versiox
会给出建议,而如果用户输入 hugo versixx
时,则不会给出建议,因为已经有两个字母不匹配 version
了。
Shell 补全
cobra默认提供 completion
子命令,可以为指定的 Shell 生成自动补全脚本,现在我们就来讲解它的用法。
直接执行 hugo completion
命令,我们可以查看它支持的几种 Shell 类型 bash
、fish
、powershell
、zsh
。
首先,明确自己环境中所使用的Shell类型,可以用以下方式查看
$ echo $0
/bin/zsh
然后,可以根据自己的shell类型,查看如何生产completion 脚本
./hugo completion zsh -h
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(hugo completion zsh)
To load completions for every new session, execute once:
#### Linux:
hugo completion zsh > "${fpath[1]}/_hugo"
#### macOS:
hugo completion zsh > $(brew --prefix)/share/zsh/site-functions/_hugo
You will need to start a new shell for this setup to take effect.
Usage:
hugo completion zsh [flags]
Flags:
-h, --help help for zsh
--no-descriptions disable completion descriptions
Global Flags:
--author string Author name for copyright attribution (default "YOUR NAME")
-v, --verbose verbose output
最后,执行对应的指令,生产completion 脚本,并配置 .profile 或者 .zshrc 或者.bashrc (根据自己的环境),加载completion脚本,即可实现命令自动补全。
最后
本打算附上官方地址,但无法过审。GH 上搜索cobra, 认准spf13/cobra.