目录

golang命令行库Cobra的使用

摘要

Cobra提供简单的接口来创建强大的现代化CLI接口,非常多知名的开源项目使用了 cobra 库构建命令行,如Kubernetes、Hugo、etcd等

cobra提供的功能

  • 简易的子命令行模式,如 hugo server, hogo fetch等等

  • 完全兼容posix命令行模式

  • 嵌套子命令subcommand

  • 支持全局,局部,串联flags

  • 使用Cobra很容易的生成应用程序和命令,使用cobra create app和cobra add subname

  • 如果命令输入错误,将提供智能建议,如 hugo srver,将提示srver没有,是否是hugo server

  • 自动生成commands和flags的帮助信息

  • 自动生成详细的help信息,如app help

  • 自动识别-h,–help帮助flag

  • 自动生成应用程序在bash下命令自动完成功能

  • 自动生成应用程序的man手册

  • 命令行别名

  • 灵活定义help和usage信息

  • 可选的紧密集成的viper apps

概念

Cobra是建立在结构的命令、参数和标志之上。

  • 命令(command) 表示 操作

  • 标志(flags) 用来控制命令(command)的具体行为

  • 参数(args)

    最好的应用程序就像读取句子。用户会知道如何使用本机应用程序,因为他们将理解如何使用它。

比如下面的例子,server是命令,port是标志:

1
hugo server --port=1111

相关操作

安装

使用Cobra很简单。首先,使用go get安装最新版本

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

将cobra下载完成后,GOPATH/bin目录会生成一个cobra可执行程序,通过这个程序我们可以初始化一个cobra代码框架。

初始化一个项目

1
2
3
4
5
6
7
C:\Users\xx\Desktop\git\cobra>go mod init times
C:\Users\xx\Desktop\git\cobra>cobra init --pkg-name times

或者 
cobra init --pkg-name github.com/spf13/newApp 
或者 
cobra init --pkg-name github.com/spf13/newApp path/to/newApp 

初始化之后可以看到,cobra命令在项目目录底下创建了开发框架:

https://xieys.club/images/posts/image-20210729165212791.png

root.go代码中有一个rootCmd变量

  • l Use: 命令。
  • l Short: 命令的简短描述。
  • l Long: 命令的详细描述,在帮助命令,如$ [appName] -h或者$ [appName] [command] -h时会显示此程序和此命令的字符串。
  • l Run: 命令执行入口,函数主要写在这个模块中。
  • l 函数initConfig(): 用来初始化viper配置文件位置,监听变化。
  • l 函数init(): 定义flag和配置处理。

通过cobra命令添加子命令

1
2
在上面创建的项目基础上添加子命令
C:\Users\xx\Desktop\git\cobra>cobra add show

此时,cmd目录会多一个show.go的文件,我们可以在这个文件中定义show具体执行的操作,这里的show就是上面说的概念里说的command(命令)

使用标志

标志(flags)用来控制command的具体行为。根据选项的作用范围,可以把选项分为两类:

persistent 全局

对于persistent类型的选项,即可以设置给该Command,又可以设置给该Command的子Command。对于一些全局性的选项,比较适合设置为persistent类型,比如控制输出的verbose选项。

local 局部

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

示例:

 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
var (
   //Foo  = new(string)
   //Print string
   //Show bool
   showL bool
   PrintL string
   FooL = new(string)
)
func init() {
   //下面定义了一个Flag foo,foo后面接的值会被赋值给Foo
   //Foo = testCmd.PersistentFlags().String("foo","","A help foo")
   ////下面定义了一个Flag print,print后面的值会被赋值给Print变量
   //testCmd.PersistentFlags().StringVar(&Print,"print","","print")
   ////下面定义了一个Flag show,show默认为false,有两种调用方式 --show \ -s ,命令后面接了show则上面定义的show变量就会变成true
   //testCmd.PersistentFlags().BoolVarP(&Show,"show","s",false,"show")

   // 下面定义了一个Flag show,show默认为false,两种条用方式 --show\-s ,命令后面接了show则上面定义的show变量就会变成true
   showL = *testCmd.Flags().BoolP("showL", "S", false, "show")
   // 下面定义了一个Flag print ,print后面的值会被赋值给Print变量
   testCmd.Flags().StringVar(&PrintL, "printL", "", "print")
   // 下面定义了一个Flag foo, foo后面接的值会被赋值给Foo
   FooL = testCmd.Flags().String("fooL", "", "A help for foo")
   rootCmd.AddCommand(testCmd)

}

必须的标记

默认情况下的选项都是可选的,但一些用例要求用户必须设置某些选项,这种情况cobra也是支持的,通过command的MarkFlagRequired方法标记该选项即可

1
2
3
4
5
6
7
全局标记设置方法
rootCmd.PersistentFlags().StringVar(&Region,"region","r","","AWS region (required)")
rootCmd.MarkPersistentFlagRequired("region")

局部标记设置方法
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")

位置参数

cobra默认提供了一些验证方法:

1、NoArgs - 如果存在任何位置参数,该命令将报错

2、ArbitraryArgs - 该命令会接受任何位置参数

3、OnlyValidArgs - 如果有任何位置参数不在命令ValidArgs字段中,该命令将报错

4、MinimumNArgs(int) - 至少要有N个位置参数,否则报错

5、MaximumNArgs(int) - 如果位置参数超过N个将报错

6、ExactArgs(int) - 必须有N个位置参数,否则报错

7、ExactValidArgs(int) 必须有N个位置参数,且都在命令的ValidArgs字段中,否则报错

8、RangeArgs(min,max) - 如果位置参数的个数不在区间min和max之中,报错

比如要让Command cmdTimes至少有一个位置参数,可以这样初始化它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var testCmd = &cobra.Command{
   Use:   "test",
   //定义arguments数量最少为1个
   Args: cobra.MinimumNArgs(1),
   Short: "short usage",
   Long: `long usage`,
   Run: func(cmd *cobra.Command, args []string) {
      if showL{
         fmt.Println("Show")
      }
      fmt.Println("Print",PrintL)
      fmt.Println("Foo",*FooL)
   },
}

简单示例

这里就不使用脚手架,直接使用导入

 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
package main

import (
  "fmt"
  "strings"

  "github.com/spf13/cobra"
)

func main() {
  var echoTimes int

  var cmdPrint = &cobra.Command{
    Use:   "print [string to print]",
    Short: "Print anything to the screen",
    Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Println("Print: " + strings.Join(args, " "))
    },
  }

  var cmdEcho = &cobra.Command{
    Use:   "echo [string to echo]",
    Short: "Echo anything to the screen",
    Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Println("Print: " + strings.Join(args, " "))
    },
  }

  var cmdTimes = &cobra.Command{
    Use:   "times [# times] [string to echo]",
    Short: "Echo anything to the screen more times",
    Long: `echo things multiple times back to the user by providing
a count and a string.`,
    Args: cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
      for i := 0; i < echoTimes; i++ {
        fmt.Println("Echo: " + strings.Join(args, " "))
      }
    },
  }

  cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")

  var rootCmd = &cobra.Command{Use: "app"}
  rootCmd.AddCommand(cmdPrint, cmdEcho)
  cmdEcho.AddCommand(cmdTimes)
  rootCmd.Execute()
}

help命令

当你的程序有子命令时,Cobra 会自动给你程序添加help命令。当你运行‘app help’,会调用help命令。另外,help同样支持其它输入命令。例如,你有一个没有任何其它配置的命令叫‘create’,当你调用‘app help create’ Corbra 将会起作用。

例子

下面的输入是 Cobra 自动生成的。除了命令和标志的定义,其它不再需要。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cobra help

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 [command]

Available Commands:
  add         Add a command to a Cobra Application
  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
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.

help 就跟其它命令一样,并没有特殊的逻辑或行为。事实上,你也可以提供你自己help如果你想的话。

自定制help

你能为默认的命令,提供你自己的help命令或模板。使用下面的方法:

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

后2个也将适用于任何子命令

使用信息

当用户提供无效的标记或命令,Cobra 将会返回用法

列子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
C:\Users\xx\Desktop\git\devops>cobra --ss
Error: unknown flag: --ss
Usage:
  cobra [command]

Available Commands:
  add         Add a command to a Cobra Application
  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
  -l, --license string   name of license for the project
      --viper            use Viper for configuration (default true)

Use "cobra [command] --help" for more information about a command.

自定制使用信息

你能提供你自己的用法函数或模板给 Cobra 使用。 比如帮助,方法和模板都可以重写。

1
2
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

版本标记

如果Version字段设置到了根命令,Cobra 会提供了一个顶层 ‘–version’标记。运行带上‘–version’标记的程序,将会按照模板版本信息。模板可以通过cmd.SetVersionTemplate(s string)方法修改

运行前和运行后的钩子

在命令运行前或运行后,再运行方法非常容易。PersistentPreRunPreRun方法将会在Run之前执行。PersistentPostRunPostRun方法将会在Run之后执行。Persistent*Run方法会被子命令继承,如果它们自己没有定义的话。这些方法将按照下面的属性执行:

  • PersistentPreRun
  • PreRun
  • Run
  • PostRun
  • PersistentPostRun

下面的例子,2个命令都使用了上面的特性。当子命令执行的时候,它将执行根命令的PersistentPreRun,但不会执行根命令的PersistentPostRun

 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
package main
import (
  "fmt"

  "github.com/spf13/cobra"
)

func main() {

  var rootCmd = &cobra.Command{
    Use:   "root [sub]",
    Short: "My root command",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
    },
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
    },
  }

  var subCmd = &cobra.Command{
    Use:   "sub [no options!]",
    Short: "My subcommand",
    PreRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd Run with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
      fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args)
    },
  }

  rootCmd.AddCommand(subCmd)

  rootCmd.SetArgs([]string{""})
  rootCmd.Execute()
  fmt.Println()
  rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
  rootCmd.Execute()
}

输出
Inside rootCmd PersistentPreRun with args: []
Inside rootCmd PreRun with args: []
Inside rootCmd Run with args: []
Inside rootCmd PostRun with args: []
Inside rootCmd PersistentPostRun with args: []

Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
Inside subCmd Run with args: [arg1 arg2]
Inside subCmd PostRun with args: [arg1 arg2]
Inside subCmd PersistentPostRun with args: [arg1 arg2]

处理“未知命令” 的建议

Cobra 会自动输出建议,当遇到“unknown command”错误时。这使得当输入错误时, Cobra 的行为类似git命令。例如:

1
2
3
4
5
6
7
$ hugo srever
Error: unknown command "srever" for "hugo"

Did you mean this?
        server

Run 'hugo --help' for usage.

建议会基于注册的子命令自动生成。使用了Levenshtein distance的实现。每一个注册的命令会匹配2个距离(忽略大小写)来提供建议。

如果你希望在你的命令里,禁用建议或虚弱字符串的距离,使用:

1
2
3
command.DisableSuggestions = true
或
command.SuggestionsMinimumDistance = 1

你可以通过SuggestFor来给命令提供明确的名词建议。这个特性允许当字符串不相近,但是意思与你的命令相近,别切你也不想给该命令设置别名。比如:

1
2
3
4
5
6
7
$ kubectl remove
Error: unknown command "remove" for "kubectl"

Did you mean this?
        delete

Run 'kubectl help' for usage.

生成命令文档

Cobra 可以基于子命令,标记,等生成文档。以以下格式: