Go 语言编程 — Cobra 指令行工具

目录

Cobra(眼镜蛇)

在这里插入图片描述

Github:https://github.com/spf13/cobra

Cobra 是一个第三方 Golang 包,是一个应用程序生成框架,用于创建自己的应用程序或命令行(Command)程序,从而开发以 Cobra 为基础的应用。目前 Docker、Kubernetes、Hugo 等著名项目都使用了 Cobra。

Cobra 提供的功能

  • 完全兼容 POSIX 命令行模式。
  • 支持嵌套子命令(sub-command),如:app server, app fetch。
  • 支持全局,局部,串联 Flags。
  • 支持应用程序和命令行程序的创建,例如:cobra create appname 和 cobra add cmdname。
  • 如果命令输入错误,支持智能提醒。
  • 支持自动生成 commands 和 flags 的 help 信息。
  • 支持自动识别 -h,–help 选型。
  • 支持自动生成 man 手册。
  • 支持命令行别名。
  • 支持自定义 help 和 usage 信息。
  • 可选的紧密集成 viper 配置工具库。

Cobra 的核心概念

  • commands:命令行,代表行为。可细分为 rootCmd 和 subCmd。
  • arguments:命令行参数。
  • flags:命令行选型,代表对行为的改变。通常以 - 或 – 标识。

执行命令行程序时的一般格式为:

APPNAME COMMAND ARG --FLAG

# e.g.
git clone <URL> --bare

Cobra 的使用

在这里插入图片描述

初始化应用程序的项目框架

通过 cobra init 指令来初始化一个应用程序的项目框架,同时还会自动生成 RootCmd(根命令)的代码。

  • GO111MODULE="off"
$ go get -u github.com/spf13/cobra/cobra

$ mkdir $GOPATH/src/cobrademo
$ cd $GOPATH/src/cobrademo

$ cobra init --pkg-name cobrademo
$ go build
  • GO111MODULE="on"
$ go get -u github.com/spf13/cobra/cobra

$ mkdir cobrademo
$ cd cobrademo

$ cobra init --pkg-name cobrademo
$ go mod init cobrademo
$ go build

项目的目录树:

$ tree -T 1
.
├── cmd
│   └── root.go
├── cobrademo
├── go.mod
├── go.sum
├── LICENSE
└── main.go

main.go

main.go 非常简约,只做一件事:初始化 Cobra Application 的 RootCmd。

// file: /root/cobrademo/main.go


package main

import "cobrademo/cmd"

func main() {
    cmd.Execute()
}

import cmd package 之后就完成了 cmd 包内所有局部变量的初始化,最主要的就是 rootCmd 变量:

// file: /root/cobrademo/cmd/root.go

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
    Use:   "cobrademo",
    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) { },
}

可见,初始的,Cobra Application 的 RootCmd 不具备任何逻辑功能,仅仅是输出一些描述信息。

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

生成应用程序的子命令(SubCmd)

我们可以通过 cobra add 指令来为 RootCmd 生成 SubCmd。

$ cobra add image
image created at /root/cobrademo

$ cobra add container
container created at /root/cobrademo

当前的目录树:

$ tree -T 1
.
├── cmd
│   ├── container.go
│   ├── image.go
│   └── root.go
├── cobrademo
├── go.mod
├── go.sum
├── LICENSE
└── main.go

上述两条指令,分别生成了 cobrademo 程序中 image 和 container 子命令的代码文件,通常存放在 /cmd 目录下。父命令和子命令之间的关系通过 AddCommand 方法来确定,通常被实现在子命令的 init() 函数中。

// file: /root/cobrademo/cmd/container.go

// containerCmd represents the container command
var containerCmd = &cobra.Command{
    Use:   "container",
    Short: "A brief description of your command",
    Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. 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.`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("container called")
    },
}

func init() {
    rootCmd.AddCommand(containerCmd)

    // Here you will define your flags and configuration settings.

    // Cobra supports Persistent Flags which will work for this command
    // and all subcommands, e.g.:
    // containerCmd.PersistentFlags().String("foo", "", "A help for foo")

    // Cobra supports local flags which will only run when this command
    // is called directly, e.g.:
    // containerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

同样的,子命令具体的功能逻辑也需要开发者自己来实现,初始的只是输出一些描述信息。但父子命令的关系以及相关的说明文档,Cobra 已经自动的完成了。

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

Usage:
  cobrademo [command]

Available Commands:
  container   A brief description of your command
  help        Help about any command
  image       A brief description of your command

Flags:
      --config string   config file (default is $HOME/.cobrademo.yaml)
  -h, --help            help for cobrademo
  -t, --toggle          Help message for toggle

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

实现 Command 的功能

目前为止我们为 cobrademo 应用程序添加了三个 Command:

  • rootCmd:cobra init 时生成,即 cobrademo 应用程序本身。
  • imageCmd:cobrademo 的子命令程序。
  • containerCmd:cobrademo 的子命令程序。

当我们需要为 RootCmd 实现逻辑功能时,只需要编辑 cmd/root.go,找到变量 rootCmd 的初始化过程并为之实现 Run 方法:

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
    Use:   "cobrademo",
    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.`,
    Run: func(cmd *cobra.Command, args []string) {
        // rootCmd 的逻辑实现
        fmt.Println("cobra demo program")
    },
}

再次编译并运行:

$ ./cobrademo
cobra demo program

其他的 SubCmd(image、container)以此类推,即可实现其各自的逻辑,通常是在别的 Package 中完成了具体的逻辑实现了,然后再收敛到 Run 方法中进行调用。可见基于 Cobra 框架,开发者可以非常简易的实现一个基于指令行的应用程序。

Command 执行的操作是通过 Command.Run 方法实现的。Cobra 还支持我们在 Run 方法执行的前后执行一些其它的操作,以下是各类 Run 方法的执行顺序:

  1. PersistentPreRun
  2. PreRun
  3. Run
  4. PostRun
  5. PersistentPostRun

其中的 PersistentPreRun 方法和 PersistentPostRun 方法可以伴随着任何子命令的执行。

为 Command 设定 Flags

在 Cobra 中,Flags 是用来控制 Command 具体行为的。

Flags 具有 2 种类型:

  1. Persistent:全局(持久)选项,可作用于所有的 CMDs。e.g. 下述 --verbose 选项可以被所有 CMDs 使用。
var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
  1. Local:局部选项,只能作用于指定的 CMD。e.g. 下述 --source 选项只能被 rootCmd 使用。
var Source string
rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")

另外的,Cobra 提供了 MarkFlagRequired() 方法,用于标记一个选项是 “可选的” 还是 “必选的”:

var Name string
rootCmd.Flags().StringVarP(&Name, "name", "n", "", "user name (required)")
rootCmd.MarkFlagRequired("name")

通常的,会在 Command 文件的 init() 函数中完成 Command 相应的 Flags 的定义,例如:

// file: /root/cobrademo/cmd/root.go

var cfgFile string

func init() {
    cobra.OnInitialize(initConfig)

    // 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/.cobrademo.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")
}

需要注意的是,当 Flag 定义好了,并且 Flag 需要接受 Args 时,我们还需要定义一个变量来关联这些标志,继而完成 Args 参数的接收。例如,当我们执行指令 ./cobrademo --config=test.yaml 时,上述代码中的 cfgFile 变量值就会被赋予 test.yaml

为 Command 设定 Arguments

常见的,有两种 Arguments 的使用方式:

  1. 作为 CMD 的 Arguments,例如:./cobrademo test,参数值会传入 Command 的 Run: func(cmd *cobra.Command, args []string) args 参数中。
  2. 作为 Flag 的 Arguments,例如:./cobrademo --config=test.yaml,参数值会传递到 Flag 对应的变量。

Cobra 对 Arguments 的处理,主要是两件事情:

  1. 接受输入。
  2. 进行解析和校验。

Cobra Build-in 提供了一系列的验证方法:

  • NoArgs:不使用 Args,存在则报错。
  • ArbitraryArgs:接受任何 Args。
  • OnlyValidArgs:仅接受合法的 Args,若 Arg 不在 ValidArgs 定义中,则报错。
  • MinimumNArgs(int):至少要有 N 个 Args,否则报错。
  • MaximumNArgs(int):至多可以有 N 个 Args,否则报错。
  • ExactArgs(int):必须有 N 个 Args,否则报错。
  • ExactValidArgs(int):必须有 N 个 Args,且都存在于 ValidArgs 定义中,否则报错。
  • RangeArgs(min, max):如果 Args 的个数不在 [min, max] 区间值,则报错。

例如:要让 cmdTimes 至少有一个 Arg,则可以这样初始化它:

var cmdTimes = &cobra.Command{
    Use: …
    Short: …
    Long: …
    Args: cobra.MinimumNArgs(1),
    Run:}

Cobra 也支持自定义验证,示例:直接编写 Args func。

var cmd = &cobra.Command{
	Short: "hello",
	Args: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return errors.New("requires at least one arg")
		}
		if myapp.IsValidColor(args[0]) {
			return nil
		}
		return fmt.Errorf("invalid color specified: %s", args[0])
	},
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Hello, World!")
	},
}

为 Command 设定帮助手册(Help)

Cobra 会自动为应用程序添加 help 子命令和 --help 选项,两种的效果是一样的。区别在于 help 子命令还可以接受其他子命令的名称作为 Args 参数,继而返回其他子命令的帮助手册。

例如:

$ cobrademo help image
$ cobrademo help image times

此外,开发者还可以通过下面的方式进行自定义:

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

为 Command 设定提示信息(Usage)

提示信息和帮助信息是类型的,Cobra 会自动添加,只不过提示信息是你输入了非法参数、选项或命令时才出现的。

$ ./cobrademo --test
Error: unknown flag: --test
Usage:
  cobrademo [flags]
  cobrademo [command]

Available Commands:
  container   A brief description of your command
  help        Help about any command
  image       Print images information

Flags:
      --config string   config file (default is $HOME/.cobrademo.yaml)
  -h, --help            help for cobrademo
  -t, --toggle          Help message for toggle

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

unknown flag: --test

同样可以自定义提示信息:

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

此外,如果我们输入了不正确的命令或者是选项,Cobra 还会给出模糊提示:

$ ./cobrademo im
Error: unknown command "im" for "cobrademo"

Did you mean this?
	image

Run 'cobrademo --help' for usage.
unknown command "im" for "cobrademo"

Did you mean this?
	image

生成指令文档

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

  • Markdown
  • ReStructured Text
  • Man Page

结合 viper 从配置文件接受 Args 的输入

init() 函数除了定义 Flags 和父子命令关系之外,还会完成 initConfig:从环境变量或配置文件加载配置选项,以此来覆盖或填充没有通过指令行参数进行配置的 Flags 值。

// file: /root/cobrademo/cmd/root.go
3
// initConfig reads in config file and ENV variables if set.
func initConfig() {
    if cfgFile != "" {
        // Use config file from the flag.
        viper.SetConfigFile(cfgFile)
    } else {
        // Find home directory.
        home, err := homedir.Dir()
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }

        // Search config in home directory with name ".cobrademo" (without extension).
        viper.AddConfigPath(home)
        viper.SetConfigName(".cobrademo")
    }

    viper.AutomaticEnv() // read in environment variables that match

    // If a config file is found, read it in.
    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Using config file:", viper.ConfigFileUsed())
    }
}

请浏览《Go 语言编程 — viper 配置管理工具》。

应用示例

为 image 子命令添加一个 cmdTimes 子命令,用于根据 times(次数)打印输入的单词。

package cmd

import (
    "fmt"
    "strings"

    "github.com/spf13/cobra"
)

// imageCmd represents the image command
var imageCmd = &cobra.Command{
    Use:   "image",
    Short: "Print images information",
    Long:  "Print all images information",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("image args are : " + strings.Join(args, " "))
    },
}

var echoTimes int

var cmdTimes = &cobra.Command{
    Use:   "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, " "))
        }
    },
}

func init() {
    cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
    imageCmd.AddCommand(cmdTimes)

    rootCmd.AddCommand(imageCmd)
}

重新编译并执行:

$ ./cobrademo image hello
image args are : hello

$ ./cobrademo image times -t=3 world
Echo: world
Echo: world
Echo: world

$ ./cobrademo image times -t=3
Error: requires at least 1 arg(s), only received 0
Usage:
  cobrademo image times [string to echo] [flags]

Flags:
  -h, --help        help for times
  -t, --times int   times to echo the input (default 1)

Global Flags:
      --config string   config file (default is $HOME/.cobrademo.yaml)

requires at least 1 arg(s), only received 0

注:因为我们在代码中为 cmdTimes 子命令设置了 Args 检查:cobra.MinimumNArgs(1) ,所以必须为 times 子命令传入一个参数。

参考资料

https://www.cnblogs.com/sparkdev/p/10856077.html
https://www.cnblogs.com/borey/p/5715641.html
https://www.jianshu.com/p/7abe7cff5384

猜你喜欢

转载自blog.csdn.net/Jmilk/article/details/108020715