Cobra 是一个 Go 言语开发的指令行(CLI)框架,它供给了简洁、灵敏且强壮的方法来创立指令行程序。它包含一个用于创立指令行程序的库(Cobra 库),以及一个用于快速生成基于 Cobra 库的指令行程序东西(Cobra 指令)。Cobra 是由 Go 团队成员 spf13 为 Hugo 项目创立的,并已被许多盛行的 Go 项目所选用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。

概念

Cobra 建立在指令、参数和标志这三个结构之上。要运用 Cobra 编写一个指令行程序,需求明确这三个概念。

  • 指令(COMMAND):指令表明要履行的操作。

  • 参数(ARG):是指令的参数,一般用来表明操作的对象。

  • 标志(FLAG):是指令的润饰,能够调整操作的行为。

一个好的指令行程序在运用时读起来像句子,用户会天然的理解并知道怎么运用该程序。

要编写一个好的指令行程序,需求遵从的模式是 APPNAME VERB NOUN --ADJECTIVEAPPNAME COMMAND ARG --FLAG

在这儿 VERB 代表动词,NOUN 代表名词,ADJECTIVE 代表形容词。

以下是一个实际国际中好的指令行程序的比方:

$ hugo server --port=1313

以上示例中,server 是一个指令(子指令),port 是一个标志(1313 是标志的参数,但不是指令的参数 ARG)。

下面是一个 git 指令的比方:

$ git clone URL --bare

以上示例中,clone 是一个指令(子指令),URL 是指令的参数,bare 是标志。

快速开始

要运用 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 https://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 文件,作为程序的启动进口。

hugo/main.go

package main
import (
	"hugo/cmd"
)
func main() {
	cmd.Execute()
}

main.go 代码完成十分简略,只在 main 函数中调用了 cmd.Execute() 函数,来履行指令。

编译并运转指令

现在,咱们就能够编译并运转这个指令行程序了。

# 编译
$ go build -o hugo
# 履行
$ ./hugo
run hugo...

笔记:示例代码里没有打印 Run 函数的 args 参数内容,你能够自行打印看看成果(提示:args 为指令行参数列表)。

以上咱们编译并履行了 hugo 程序,输出内容正是 cobra.Command 结构体中 Run 函数内部代码的履行成果。

咱们还能够运用 --help 检查这个指令行程序的运用协助。

$ ./hugo --help
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://gohugo.io
Usage:
  hugo [flags]
Flags:
  -h, --help   help for hugo

这儿打印了 cobra.Command 结构体中 Long 特点的内容,假如 Long 特点不存在,则打印 Short 特点内容。

hugo 指令用法为 hugo [flags],如 hugo --help

这个指令行程序主动支撑了 -h/--help 标志。

以上就是运用 Cobra 编写一个指令行程序最常见的套路,这也是 Cobra 引荐写法。

当时项目目录结构如下:

$ tree hugo
hugo
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go

Cobra 程序目录结构根本如此,main.go 作为指令行程序的进口,不要写过多的业务逻辑,所有指令都应该放在 cmd/ 目录下,以后不论编写多么杂乱的指令行程序都能够这么来规划。

增加子指令

与界说 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)
}

现在从头编译并运转指令行程序。

$ go build -o hugo
$ ./hugo version                       
Hugo Static Site Generator v0.9 -- HEAD

能够发现 version 指令现已被加入进来了。

再次检查协助信息:

$ ./hugo -h
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://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.

这次的协助信息更为丰富,除了能够运用 hugo [flags] 语法,由于子指令的加入,又多了一个 hugo [command] 语法能够运用,如 hugo version

现在有三个可用指令:

completion 能够为指定的 Shell 生成主动补全脚本,将在 Shell 补全 末节进行解说。

help 用来检查协助,同 -h/--help 相似,能够运用 hugo help command 语法检查 command 指令的协助信息。

version 为新增加的子指令。

检查子指令协助信息:

$ ./hugo help version
All software has versions. This is Hugo's
Usage:
  hugo version [flags]
Flags:
  -h, --help   help for version

运用指令行标志

Cobra 完美适配 pflag,结合 pflag 能够更灵敏的运用标志功用。

提示:对 pflag 不熟悉的读者能够参阅我的另一篇文章《Go 指令行参数解析东西 pflag 运用》。

耐久标志

假如一个标志是耐久的,则意味着该标志将可用于它所分配的指令以及该指令下的所有子指令。

关于全局标志,能够界说在根指令 rootCmd 上。

var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")

本地标志

标志也能够是本地的,这意味着它只适用于该指定指令。

var Source string
rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

父指令的本地标志

默许情况下,Cobra 仅解析目标指令上的本地标志,忽略父指令上的本地标志。经过在父指令上启用 Command.TraverseChildren 特点,Cobra 将在履行目标指令之前解析每个指令的本地标志。

var rootCmd = &cobra.Command{
	Use:   "hugo",
	TraverseChildren: true,
}

提示:假如你不理解,没关系,继续往下看,稍后会有示例代码演示解说。

必选标志

默许情况下,标志是可选的。咱们能够将其符号为必选,假如没有供给,则会报错。

var Region string
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

界说好以上几个标志后,为了展示效果,咱们对 rootCmd.Run 办法做些修正,别离打印 VerboseSourceRegion 几个变量。

var rootCmd = &cobra.Command{
	Use:   "hugo",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("run hugo...")
		fmt.Printf("Verbose: %v\n", Verbose)
		fmt.Printf("Source: %v\n", Source)
		fmt.Printf("Region: %v\n", Region)
	},
}

别的,为了测验启用 Command.TraverseChildren 的效果,我又增加了一个 print 子指令。

hugo/cmd/print.go

var printCmd = &cobra.Command{
	Use: "print [OPTIONS] [COMMANDS]",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("run print...")
		fmt.Printf("printFlag: %v\n", printFlag)
		fmt.Printf("Source: %v\n", Source)
	},
}
func init() {
	rootCmd.AddCommand(printCmd)
	// 本地标志
	printCmd.Flags().StringVarP(&printFlag, "flag", "f", "", "print flag for local")
}

现在,咱们从头编译并运转 hugo,来对上面增加的这几个标志进行测验。

$ go build -o hugo
$ ./hugo -h                    
A Fast and Flexible Static Site Generator built with
                love by spf13 and friends in Go.
                Complete documentation is available at https://gohugo.io
Usage:
  hugo [flags]
  hugo [command]
Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  print       
  version     Print the version number of Hugo
Flags:
  -h, --help            help for hugo
  -r, --region string   AWS region (required)
  -s, --source string   Source directory to read from
  -v, --verbose         verbose output
Use "hugo [command] --help" for more information about a command.

以上协助信息明晰明晰,我就不过多解说了。

履行 hugo 指令:

$ ./hugo -r test-region
run hugo...
Verbose: false
Source: 
Region: test-region

现在 -r/--region 为必选标志,不传将会得到 Error: required flag(s) "region" not set 报错。

履行 print 子指令:

$ ./hugo print -f test-flag
run print...
printFlag: test-flag
Source: 

以上履行成果能够发现,父指令的标志 Source 内容为空。

现在运用如下指令履行 print 子指令:

$ ./hugo -s test-source print -f test-flag
run print...
printFlag: test-flag
Source: test-source

print 子指令前,咱们指定了 -s test-source 标志,-s/--source 是父指令 hugo 的标志,也能够被正确解析,这就是启用 Command.TraverseChildren 的效果。

假如咱们将 rootCmdTraverseChildren 特点置为 false,则会得到 Error: unknown shorthand flag: 's' in -s 报错。

# 指定 rootCmd.TraverseChildren = false 后,从头编译程序
$ go build -o hugo
# 履行相同的指令,现在会得到报错
$ ./hugo -s test-source print -f test-flag
Error: unknown shorthand flag: 's' in -s
Usage:
  hugo print [OPTIONS] [COMMANDS] [flags]
Flags:
  -f, --flag string   print flag for local
  -h, --help          help for print
Global Flags:
  -v, --verbose   verbose output
unknown shorthand flag: 's' in -s

处理装备

除了将指令行标志的值绑定到变量,咱们也能够将标志绑定到 Viper,这样就能够运用 viper.Get() 来获取标志的值了。

var author string
func init() {
  rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
  viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}

提示:对 Viper 不熟悉的读者能够参阅我的另一篇文章《在 Go 中怎么运用 Viper 来办理装备》。

别的,咱们能够运用 cobra.OnInitialize() 来初始化装备文件。

var cfgFile string
func init() {
	cobra.OnInitialize(initConfig)
	rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file")
}
func initConfig() {
	if cfgFile != "" {
		viper.SetConfigFile(cfgFile)
	} else {
		home, err := homedir.Dir()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		viper.AddConfigPath(home)
		viper.SetConfigName(".cobra")
	}
	if err := viper.ReadInConfig(); err != nil {
		fmt.Println("Can't read config:", err)
		os.Exit(1)
	}
}

传递给 cobra.OnInitialize() 的函数 initConfig 函数将在调用指令的 Execute 办法时运转。

为了展示运用 Cobra 处理装备的效果,需求修正 rootCmd.Run 函数的打印代码:

var rootCmd = &cobra.Command{
	Use:   "hugo",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("run hugo...")
		fmt.Printf("Verbose: %v\n", Verbose)
		fmt.Printf("Source: %v\n", Source)
		fmt.Printf("Region: %v\n", Region)
		fmt.Printf("Author: %v\n", viper.Get("author"))
		fmt.Printf("Config: %v\n", viper.AllSettings())
	},
}

供给 config.yaml 装备文件内容如下:

username: jianghushinian
password: 123456
server:
  ip: 127.0.0.1
  port: 8080

现在从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 履行
$ ./hugo -r test-region --author jianghushinian -c ./config.yaml 
run hugo...
Verbose: false
Source: 
Region: test-region
Author: jianghushinian
Config: map[author:jianghushinian password:123456 server:map[ip:127.0.0.1 port:8080] username:jianghushinian]

笔记:Cobra 一起支撑 pflag 和 Viper 两个库,实际上这三个库出自同一作者 spf13。

参数验证

在履行指令行程序时,咱们或许需求对指令参数进行合法性验证,cobra.CommandArgs 特点供给了此功用。

Args 特点类型为一个函数:func(cmd *Command, args []string) error,能够用来验证参数。

Cobra 内置了以下验证函数:

  • NoArgs:假如存在任何指令参数,该指令将报错。

  • ArbitraryArgs:该指令将承受恣意参数。

  • OnlyValidArgs:假如有任何指令参数不在 CommandValidArgs 字段中,该指令将报错。

  • MinimumNArgs(int):假如没有至少 N 个指令参数,该指令将报错。

  • MaximumNArgs(int):假如有超越 N 个指令参数,该指令将报错。

  • ExactArgs(int):假如指令参数个数不为 N,该指令将报错。

  • ExactValidArgs(int):假如指令参数个数不为 N,或许有任何指令参数不在 CommandValidArgs 字段中,该指令将报错。

  • 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 个则报错
}

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 两个指令参数满意验证函数的要求
$ ./hugo version a b  
Hugo Static Site Generator v0.9 -- HEAD
# 超越两个参数则报错
$ ./hugo version a b c
Error: accepts at most 2 arg(s), received 3

当然,咱们也能够自界说验证函数:

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
	},
}

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 4 个参数满意条件
$ ./hugo print a b c d
run print...
args: [a b c d]
# 没有参数则报错
$ ./hugo print  
Error: requires at least one arg
# 第一个参数不满意验证函数逻辑,也会报错
$ ./hugo print x      
Error: first argument must be 'a'

Hooks

在履行 Run 函数前后,我么能够履行一些钩子函数,其效果和履行次序如下:

  1. PersistentPreRun:在 PreRun 函数履行之前履行,对此指令的子指令相同收效。

  2. PreRun:在 Run 函数履行之前履行。

  3. Run:履行指令时调用的函数,用来编写指令的业务逻辑。

  4. PostRun:在 Run 函数履行之后履行。

  5. PersistentPostRun:在 PostRun 函数履行之后履行,对此指令的子指令相同收效。

修正 rootCmd 如下:

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")
	},
}

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 履行
$ ./hugo
hugo PersistentPreRun
hugo PreRun
run hugo...
hugo PostRun
hugo PersistentPostRun

输出次序符合预期。

其间 PersistentPreRunPersistentPostRun 两个函数对子指令相同收效。

$ ./hugo version
hugo PersistentPreRun
Hugo Static Site Generator v0.9 -- HEAD
hugo PersistentPostRun

以上几个函数都有对应的 <Hooks>E 版本,E 表明 Error,即函数履行出错将会回来 Error,履行次序不变:

  1. PersistentPreRunE

  2. PreRunE

  3. RunE

  4. PostRunE

  5. PersistentPostRunE

假如界说了 <Hooks>E 函数,则 <Hooks> 函数不会履行。比方一起界说了 RunRunE,则只会履行 RunE,不会履行 Run,其他 Hooks 函数同理。

var rootCmd = &cobra.Command{
	Use:   "hugo",
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("hugo PersistentPreRun")
	},
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("hugo PersistentPreRunE")
		return nil
	},
	PreRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("hugo PreRun")
	},
	PreRunE: func(cmd *cobra.Command, args []string) error {
		fmt.Println("hugo PreRunE")
		return errors.New("PreRunE err")
	},
	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")
	},
}

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 履行
$ ./hugo          
hugo PersistentPreRunE
hugo PreRunE
Error: PreRunE err
Usage:
  hugo [flags]
  hugo [command]
Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  print       
  version     Print the version number of Hugo
Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -c, --config string   config file
  -h, --help            help for hugo
  -r, --region string   AWS region (required)
  -s, --source string   Source directory to read from
  -v, --verbose         verbose output
Use "hugo [command] --help" for more information about a command.
PreRunE err

能够发现,尽管一起界说了 PersistentPreRunPersistentPreRunE 两个钩子函数,但只有 PersistentPreRunE 会被履行。

在履行 PreRunE 时回来了一个过错 PreRunE err,程序会停止运转并打印过错信息。

假如子指令界说了自己的 Persistent*Run 函数,则不会继承父指令的 Persistent*Run 函数。

var versionCmd = &cobra.Command{
	Use:   "version",
	PersistentPreRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("version PersistentPreRun")
	},
	PreRun: func(cmd *cobra.Command, args []string) {
		fmt.Println("version PreRun")
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
	},
}

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 履行子指令
$ ./hugo version  
version PersistentPreRun
version PreRun
Hugo Static Site Generator v0.9 -- HEAD
hugo PersistentPostRun

界说自己的 Help 指令

假如你对 Cobra 主动生成的协助指令不满意,咱们能够自界说协助指令或模板。

cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)

Cobra 供给了三个办法来完成自界说协助指令,后两者也适用于任何子指令。

默许情况下,咱们能够运用 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 指令检查协助信息时会履行钩子函数。

咱们能够运用 rootCmd.SetHelpCommand 来操控 help 指令输出,运用 rootCmd.SetHelpFunc 来操控 -h/--help 输出。

rootCmd.SetHelpCommand(&cobra.Command{
	Use:    "help",
	Short:  "Custom help command",
	Hidden: true,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Custom help command")
	},
})
rootCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
	fmt.Println(strings)
})

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 运用 `help` 指令检查协助信息
$ ./hugo help version
hugo PersistentPreRunE
Custom help command
hugo PersistentPostRun
# 运用 `-h` 检查根指令协助信息
$ ./hugo -h
[-h]
# 运用 `-h` 检查 version 指令协助信息
$ ./hugo version -h  
[version -h]

能够发现,运用 help 指令检查协助信息输出成果是 rootCmd.SetHelpCommandRun 函数的履行输出。运用 -h 检查协助信息输出成果是 rootCmd.SetHelpFunc 函数的履行输出,strings 代表的是指令行标志和参数列表。

现在咱们再来测验下 rootCmd.SetHelpTemplate 的效果,它用来设置协助信息模板,支撑标准的 Go Template 语法,自界说模板如下:

rootCmd.SetHelpTemplate(`Custom Help Template:
Usage:
	{{.UseLine}}
Description:
	{{.Short}}
Commands:
{{- range .Commands}}
	{{.Name}}: {{.Short}}
{{- end}}
`)

留意:为了单独测验 cmd.SetHelpTemplate(s string),我已将上面 rootCmd.SetHelpCommandrootCmd.SetHelpFunc 部分代码注释掉了。

从头编译并运转 hugo 指令:

# 编译
$ go build -o hugo
# 检查协助
$ ./hugo -h            
Custom Help Template:
Usage:
        hugo [flags]
Description:
        Hugo is a very fast static site generator
Commands:
        completion: Generate the autocompletion script for the specified shell
        help: Help about any command
        print: 
        version: Print the version number of Hugo
# 检查子指令协助
$ ./hugo help version
hugo PersistentPreRunE
Custom Help Template:
Usage:
        hugo version [flags]
Description:
        Print the version number of Hugo
Commands:
hugo PersistentPostRun

能够发现,无论运用 help 指令检查协助信息,仍是运用 -h 检查协助信息,其输出内容都遵从咱们自界说的模版格局。

界说自己的 Usage Message

当用户供给无效标志或无效指令时,Cobra 经过向用户显现 Usage 来提示用户怎么正确的运用指令。

例如,当用户输入无效的标志 --demo 时,将得到如下输出:

$ ./hugo --demo
Error: unknown flag: --demo
Usage:
  hugo [flags]
  hugo [command]
Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  print       
  version     Print the version number of Hugo
Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -c, --config string   config file
  -h, --help            help for hugo
  -s, --source string   Source directory to read from
  -v, --verbose         verbose output
Use "hugo [command] --help" for more information about a command.
unknown flag: --demo

首要程序会报错 Error: unknown flag: --demo,报错后会显现 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

当咱们输入一个不存在的指令 statu 时,git 会提示指令不存在,而且给出几个最相似指令的主张。

这个功用十分实用,幸运的是,Cobra 自带了此功用。

如下,当咱们输入一个不存在的指令 vers 时,hugo 会主动给出主张指令 version

$ ./hugo vers
Error: unknown command "vers" for "hugo"
Did you mean this?
        version
Run 'hugo --help' for usage.
unknown command "vers" for "hugo"
Did you mean this?
        version

留意⚠️:依据我的实测,要想让此功用收效,Command.TraverseChildren 特点要置为 false

假如你想完全关闭此功用,能够运用如下设置:

command.DisableSuggestions = true

或许运用如下设置调整字符串匹配的最小间隔:

command.SuggestionsMinimumDistance = 1

SuggestionsMinimumDistance 是一个正整数,表明输错的指令与正确的指令最多有几个不匹配的字符(最小间隔),才会给出主张。如当值为 1 时,用户输入 hugo versiox 会给出主张,而假如用户输入 hugo versixx 时,则不会给出主张,由于现已有两个字母不匹配 version 了。

Shell 补全

前文在讲增加子指令末节时,咱们见到过 completion 子指令,能够为指定的 Shell 生成主动补全脚本,现在咱们就来解说它的用法。

直接履行 hugo completion 指令,咱们能够检查它支撑的几种 Shell 类型 bashfishpowershellzsh

$ ./hugo completion
Generate the autocompletion script for hugo for the specified shell.
See each sub-command's help for details on how to use the generated script.
Usage:
  hugo completion [command]
Available Commands:
  bash        Generate the autocompletion script for bash
  fish        Generate the autocompletion script for fish
  powershell  Generate the autocompletion script for powershell
  zsh         Generate the autocompletion script for zsh
Flags:
  -h, --help   help for completion
Global Flags:
      --author string   Author name for copyright attribution (default "YOUR NAME")
  -v, --verbose         verbose output
Use "hugo completion [command] --help" for more information about a command.

要想知道自己正在运用的 Shell 类型,能够运用如下指令:

$ echo $0
/bin/zsh

能够发现,我运用的是 zsh,所以我就以 zsh 为例,来演示下 completion 指令补全用法。

运用 -h/--help 咱们能够检查运用说明:

$ ./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

依据协助信息,假如为当时会话供给指令行补全功用,能够运用 source <(hugo completion zsh) 指令来完成。

假如要让指令行补全功用永久收效,Cobra 则十分交心的为 Linux 和 macOS 供给了不同指令。

你能够依据提示挑选自己喜欢的方法来完成指令行补全功用。

我这儿只完成为当时会话供给指令行补全功用为例进行演示:

# 首要在项目根目录下,安装 hugo 指令行程序,安装后软件存放在 $GOPATH/bin 目录下
$ go install .
# 增加指令行补全功用
$ source <(hugo completion zsh)
# 现在指令行补全现已收效,只需求输入一个 `v`,然后按下键盘上的 `Tab` 键,指令将主动补全为 `version`
$ hugo v
# 指令已被主动补全
$ hugo version 
version PersistentPreRun
version PreRun
Hugo Static Site Generator v0.9 -- HEAD

其实将指令 source <(hugo completion zsh) 增加到 ~/.zshrc 文件中,也能完成每次进入 zsh 后主动加载 hugo 的指令行补全功用。

留意:在履行 source <(hugo completion zsh) 前需求将 rootCmd 中的钩子函数内部的 fmt.Println 代码全部注释掉,否则打印内容会被当作指令来履行,将会得到 Error: unknown command "PersistentPreRunE" for "hugo" 相似报错信息,尽管指令行补全功用仍然能够收效,但「没有消息才是最好的消息」。

生成文档

Cobra 支撑生成 MarkdownReStructured TextMan Page 三种格局文档。

这儿以生成 Markdown 格局文档为例,来演示下 Cobra 这一强壮功用。

咱们能够界说一个标志 md-docs 来决定是否生成文档:

hugo/cmd/root.go

var MarkdownDocs bool
func init() {
	rootCmd.Flags().BoolVarP(&MarkdownDocs, "md-docs", "m", false, "gen Markdown docs")
	...
}
func GenDocs() {
	if MarkdownDocs {
		if err := doc.GenMarkdownTree(rootCmd, "./docs/md"); err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}

main.go 中调用 GenDocs() 函数。

func main() {
	cmd.Execute()
	cmd.GenDocs()
}

现在,从头编译并运转 hugo 即可生成文档:

# 编译
$ go build -o hugo
# 生成文档
$ ./hugo --md-docs
# 检查生成的文档
$ tree docs/md       
docs/md
├── hugo.md
├── hugo_completion.md
├── hugo_completion_bash.md
├── hugo_completion_fish.md
├── hugo_completion_powershell.md
├── hugo_completion_zsh.md
├── hugo_print.md
└── hugo_version.md

能够发现,Cobra 不仅为 hugo 指令生成了文档,而且还生成了子指令的文档以及指令行补全的文档。

运用 Cobra 指令创立项目

文章读到这儿,咱们能够发现,其实 Cobra 项目是遵从一定套路的,目录结构、文件、模板代码都比较固定。

此刻,脚手架东西就派上用场了。Cobra 供给了 cobra-cli 指令行东西,能够经过指令的方法快速创立一个指令行项目。

安装:

$ go install github.com/spf13/cobra-cli@latest

检查运用协助:

$ cobra-cli -h
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
Usage:
  cobra-cli [command]
Available Commands:
  add         Add a command to a Cobra Application
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  init        Initialize a Cobra Application
Flags:
  -a, --author string    author name for copyright attribution (default "YOUR NAME")
      --config string    config file (default is $HOME/.cobra.yaml)
  -h, --help             help for cobra-cli
  -l, --license string   name of license for the project
      --viper            use Viper for configuration
Use "cobra-cli [command] --help" for more information about a command.

能够发现,cobra-cli 脚手架东西仅供给了少量指令和标志,所以上手难度不大。

初始化模块

要运用 cobra-cli 生成一个项目,首要要手动创立项目根目录并运用 go mod 指令进行初始化。

假设咱们要编写的指令行程序叫作 cog,模块初始化过程如下:

# 创立项目目录
$ mkdir cog
# 进入项目目录
$ cd cog
# 初始化模块
$ go mod init github.com/jianghushinian/blog-go-example/cobra/getting-started/cog

初始化指令行程序

有了初始化好的 Go 项目,咱们就能够初始化指令行程序了。

# 初始化程序
$ cobra-cli init
Your Cobra application is ready at
# 检查生成的项目目录结构
$ tree .
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go
2 directories, 5 files
# 履行指令行程序
$ go run main.go                                                                 
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.

运用 cobra-cli 初始化程序十分便利,只需求一个简略的 init 指令即可完成。

目录结构跟咱们手动编写的程序相同,只不过多了一个 LICENSE 文件,用来存放项目的开源许可证。

经过 go run main.go 履行这个指令行程序,即可打印 rootCmd.Run 的输出成果。

运用脚手架主动生成的 cog/main.go 文件内容如下:

/*
Copyright  2023 NAME HERE <EMAIL ADDRESS>
*/
package main
import "github.com/jianghushinian/blog-go-example/cobra/getting-started/cog/cmd"
func main() {
	cmd.Execute()
}

主动生成的 cog/cmd/root.go 文件内容如下:

/*
Copyright  2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
	"os"
	"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
	Use:   "cog",
	Short: "A brief description of your application",
	Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	// Uncomment the following line if your bare application
	// has an action associated with it:
	// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	err := rootCmd.Execute()
	if err != nil {
		os.Exit(1)
	}
}
func init() {
	// Here you will define your flags and configuration settings.
	// Cobra supports persistent flags, which, if defined here,
	// will be global for your application.
	// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cog.yaml)")
	// Cobra also supports local flags, which will only run
	// when this action is called directly.
	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

以上两个文件跟咱们手动编写的代码没什么两样,套路完全相同,仅有不同的是每个文件头部都会多出来一个 Copyright 头信息,用来符号代码的 LICENSE

可选标志

cobra-cli 供给了如下三个标志别离用来设置项目的作者、许可证类型、是否运用 Viper 办理装备。

$ cobra-cli init --author "jianghushinian" --license mit --viper
Your Cobra application is ready at

以上指令咱们指定可选标志后对项目进行了从头初始化。

现在 LICENSE 文件内容不再为空,而是 MIT 协议。

The MIT License (MIT)
Copyright  2023 jianghushinian
Permission is hereby granted...

而且 Go 文件 Copyright 头信息中作者信息也会被补全。

/*
Copyright  2023 jianghushinian
...
*/

笔记:cobra-cli 指令内置开源许可证支撑 GPLv2GPLv3LGPLAGPLMIT2-Clause BSD3-Clause BSD。也能够参阅官方文档来指定自界说许可证。

提示:假如你对开源许可证不熟悉,能够参阅我的另一篇文章《开源协议简介》。

增加指令

咱们能够运用 add 指令为程序增加新的指令,而且 add 指令相同支撑可选标志。

$ cobra-cli add serve
$ cobra-cli add config
$ cobra-cli add create -p 'configCmd' --author "jianghushinian" --license mit --viper 

这儿别离增加了三个指令 serveconfigcreate,前两者都是 rootCmd 的子指令,create 指令则经过 -p 'configCmd' 参数指定为 config 的子指令。

留意⚠️:运用 -p 'configCmd' 标志指定当时指令的父指令时,configCmd 有必要是小驼峰命名法,由于 cobra-cliconfig 生成的指令代码主动命名为 configCmd,而不是 config_cmd 或其他形式,这符合 Go 言语变量命名规范。

现在指令行程序目录结构如下:

$ tree .
.
├── LICENSE
├── cmd
│ ├── config.go
│ ├── create.go
│ ├── root.go
│ └── serve.go
├── go.mod
├── go.sum
└── main.go
2 directories, 8 files

能够运用如下指令履行子指令:

$ go run main.go config create
create called

其他新增加的指令同理。

运用装备替代标志

假如你不想每次生成或增加指令时都指定选项参数,则能够界说 ~/.cobra.yaml 文件来保存装备信息:

author: jianghushinian <jianghushinian007@outlook.com>
year: 2023
license: MIT
useViper: true

再次运用 init 指令初始化程序:

$ cobra-cli init
Using config file: /Users/jianghushinian/.cobra.yaml

会提示运用了 ~/.cobra.yaml 装备文件。

现在 LICENSE 文件内容格局如下:

The MIT License (MIT)
Copyright  2023 jianghushinian <jianghushinian007@outlook.com>
...

Go 文件 Copyright 头信息也会包含日期、用户名、用户邮箱。

/*
Copyright  2023 jianghushinian <jianghushinian007@outlook.com>
...
*/

假如你不想把装备保存在 ~/.cobra.yaml 中,cobra-cli 还供给了 --config 标志来指定恣意目录下的装备文件。

至此,cobra-cli 的功用咱们就都解说完成了,仍是十分便利实用的。

总结

在咱们日常开发中,编写指令行程序是必不可少,很多开源软件都具备强壮的指令行东西,如 K8s、Docker、Git 等。

一款杂乱的指令行程序通常有上百种运用组合,所以怎么组织和编写出好用的指令行程序是很检测开发者功底的,而 Cobra 则为咱们开发指令行程序供给了满意的便利。这也是为什么我将其称为指令行框架,而不仅仅是一个 Go 第三方库。

Cobra 功用十分强壮,要运用它来编写指令行程序首要要明白三个概念:指令、参数和标志。

Cobra 不仅支撑子指令,还能够完美兼容 pflag 和 Viper 包,由于这三个包都是同一个作者开发的。关于标志,Cobra 支撑耐久标志、本地标志以及将标志符号为必选。Cobra 能够将标志绑定到 Viper,便利运用 viper.Get() 来获取标志的值。关于指令行参数,Cobra 供给了不少验证函数,咱们也能够自界说验证函数。

Cobra 还供给了几个 Hooks 函数 PersistentPreRunPreRunPostRunPersistentPostRun,能够别离在履行 Run 前后来处理一段逻辑。

假如觉得 Cobra 供给的默许协助信息不能满意需求,咱们还能够界说自己的 Help 指令和 Usage Message,十分灵敏。

Cobra 还支撑不知道指令的智能提示功用以及 Shell 主动补全功用,此外,它还支撑主动生成 MarkdownReStructured TextMan Page 三种格局的文档。这对指令行东西的运用者来说十分友爱,还能极大削减开发者的工作量。

最后,Cobra 的指令行东西 cobra-cli 进一步提高了编写指令行程序的功率,十分引荐运用。

本文完好代码示例我放在了 GitHub 上,欢迎点击检查。

希望此文能对你有所协助。

联系我

  • 微信:jianghushinian
  • 邮箱:jianghushinian007@outlook.com
  • 博客地址:jianghushinian.cn/

参阅

  • Cobra 官网:cobra.dev/
  • Cobra 源码:github.com/spf13/cobra
  • Cobra 文档:pkg.go.dev/github.com/…
  • Cobra-CLI 文档:github.com/spf13/cobra…
  • 本文示例代码:github.com/jianghushin…