简介

Cobra 既是一个用来创建强大的现代 CLI 命令行的 Golang 库,也是一个生成程序应用和命令行文件的程序。

Cobra 在很多 Go 项目中都有使用,例如: KubernetesHugo, 和 Github CLI。更多使用 Cobra 的程序见这个列表

简而言之,Cobra 有两部分组成,一个是库,一个是 Cobra 程序。Cobra 库可以在你的项目中引用,Cobra 提供了简洁的接口用来构建强大的 CLI 程序。同时,Cobra 还提供了一个命令行工具,用来生成基本项目目录结构和创建子命令的代码模板,其实就是个脚手架工具。

Cobra 提供的功能:

  • 简易的子命令行模式,如 app serverapp fetch 等等
  • 完全兼容 POSIX 命令行模式
  • 嵌套子命令 subcommand
  • 支持全局,局部,串联 flags
  • 使用 Cobra 很容易的生成应用程序和命令,使用 cobra create appnamecobra add cmdname
  • 如果命令输入错误,将提供智能建议,如 app srver,将提示找不到 srver 是否是 app server
  • 自动生成 commandsflags 的帮助信息
  • 自动生成详细的 help 信息,如 app help
  • 自动识别 -h--help 帮助 flag
  • 自动生成应用程序在 bash 下命令自动完成功能
  • 自动生成应用程序的 man 手册
  • 命令行别名
  • 允许灵活的自定义 help 和 usage 信息
  • 可选的与 viper apps 紧密集成

基础概念

Cobra 由 命令 (commands)、参数 (arguments)、标志 (flags) 三部分组成。

Commands 代表动作,Args 是事物,Flags 是这些动作的修饰符。

举个栗子:

1
git clone url --bare
  • git:根命令
  • clone:子命令
  • url:参数
  • –bare:flag,标志,用来修饰一条命令的描述或者约束。

官方文档中有这样一段话:

The best applications will read like sentences when used. Users will know how to use the application because they will natively understand how to use it.

翻译过来就是:最好的程序在使用的时候就像阅读一句话,用户将很自然的知道如何该使用程序。

安装 Cobra

1
go get -u github.com/spf13/cobra/cobra

注意:安装 Cobra 并不像官方所说如此的容易,由于某些不可抗拒的原因,你大概率会安装失败。请参考 《使用 Goproxy 解决 go get 的网络问题》

开始使用 Cobra

注:以下的很多操作,Cobra 提供了一个很方便的 CLI 生成器可以使用,详细使用见这里。不过建议第一次使用 Cobra 的小伙伴还是手动操作以下比较稳。

目录结构

Cobra 官方文档给出了 Cobra 程序一般的目录组织结构,Cobra 的 CLI 工具默认也是遵循这样的目录结构,当然,你也可以遵循自己的接口:

1
2
3
4
5
6
7
  ▾ appName/
    ▾ cmd/
        add.go
        your.go
        commands.go
        here.go
      main.go

然后就是在 main.go 中初始化 Cobra,一般来说,main.go 也不会做别的事情。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import (
  "fmt"
  "os"

  "{pathToYourApp}/cmd"
)

func main() {
  cmd.Execute()
}

创建根命令

如果你想要使用 Cobra 库,你必须要有一个 main.go 和一个根命令的文件。然后在此基础上添加新的命令。

Cobra 不需要特殊的构造函数,app/cmd/root.go 文件的内容可以像下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
	Use:   "cmdName",
	Short: "命令的简短的介绍",
	Long: `命令较为详细的介绍
		可以换行
	`,
	Run: func(cmd *cobra.Command, args []string) {
		// 这里是调用根命令时会执行的操作
		fmt.Println("Hello world")
	},
}

// Execute 在根命令上添加所有的子命令并设置标志。
// 这个函数在 main.main() 中调用,且只应该被调用一次
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

添加 Flags

你还可以在 init() 函数中定义命令的 flags 和相关的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package cmd

import (
   "fmt"
   "os"

   "github.com/mitchellh/go-homedir"
   "github.com/spf13/cobra"
   "github.com/spf13/viper"
)

var (
   cfgFile     string
   userLicense string

   rootCmd = &cobra.Command{
       Use:   "idown",
       Short: "命令的简短的介绍",
       Long: `命令较为详细的介绍
           可以换行
       `,
       Run: func(cmd *cobra.Command, args []string) {
           // 这里是调用命令时会执行的操作
           fmt.Println(cmd.PersistentFlags().Lookup("author").Value)
           fmt.Println(cmd.PersistentFlags().Lookup("config").Value)
           fmt.Println(cmd.PersistentFlags().Lookup("viper").Value)
           fmt.Println(cmd.PersistentFlags().Lookup("license").Value)
       },
   }
)

// Execute 在根命令上添加所有的子命令并设置标志。
// 这个函数在 main.main() 中调用,且只应该被调用一次
func Execute() {
   if err := rootCmd.Execute(); err != nil {
       fmt.Println(err)
       os.Exit(1)
   }
}

func init() {
   cobra.OnInitialize(initConfig)

   // 在这里注册命令的标志
   rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
   rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
   rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
   rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
   viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
   viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
   viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
   viper.SetDefault("license", "apache")
}

func er(msg interface{}) {
   fmt.Println("Error: ", msg)
   os.Exit(1)
}

func initConfig() {
   if cfgFile != "" {
       // 使用标记传递的配置文件
       viper.SetConfigFile(cfgFile)
   } else {
       // 查找家目录找配置
       home, err := homedir.Dir()
       if err != nil {
           er(err)
       }

       viper.AddConfigPath(home)
       // 在家目录查找名字是 .cobra 的配置文件,注意,不带配置文件后缀
       viper.SetConfigName(".cobra")
   }

   viper.AutomaticEnv()
   if err := viper.ReadInConfig(); err == nil {
       fmt.Println("Using config file: " + viper.ConfigFileUsed())
   }
}

然后你可以使用下面的命令执行程序看看效果:

1
go run main.go --viper=false --config=/home/user/config.yml --license=MIT

创建其他子命令

其它的子命令通常定义在 cmd/ 目录下的单独的文件内

比如,我们想要创建一个 version 的子命令,可以这样做:

  1. 创建 cmd/version.go 文件
  2. version.go 中添加下面的代码:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

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

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print the version number of Your App",
	Long:  "All software has versions. This is Your App's",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("version 0.0.1")
	},
}

执行下面的命令查看效果:

1
go run main.go version

使用标志

标志(Flag)提供了一些修改项,借此来控制命令该如何运行。

给命令分配标志

定义标志之后,需要定义一个变量关来联标志。

1
2
var Verbose bool
var Source string

持久标志

“持久"意味着该标志分配给它的命令以及该命令下的每个命令。对于全局标志就是在根命令上绑定的持久标志。

例如:

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

局部标志

标志也可以作为局部标志应用到指定的命令。

1
localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

在父命令中访问局部标志

Cobra 默认只在目标命令上解析标志,父命令忽略任何局部标志。通过设置 Command.TraverseChildrentrue,Cobra 将会在执行任意目标命令前解析标志。

如下示例:

1
2
3
4
var newCmd = &cobra.Command{
	Use:   "print [OPTIONS] [COMMANDS]",
	TraverseChildren: true,
}

使用配置文件绑定标志

你也可以使用 viper 绑定标志:

1
2
3
4
5
6
var author string

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

上面的示例中,author 使用 viper 绑定,但需要注意的是:如果用户没有指定 --author 标志的话,author 变量并不会从配置文件中被赋值。

更多关于 viper 绑定参数的内容请移步:viper 文档

必须要指定的标志

所有的标志默认都是可选的,如果有一些标志要求用户必须指定的话,可以标记为 required

1
2
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")