• Cobra 命令行工具


    1. Cobra 介绍

    Cobra 是一个用来创建命令行的 golang 库,同时也是一个用于生成应用和命令行文件的程序。

    1.1 概念

    Cobra 结构由三部分组成:命令 (commands)、参数 (arguments)、标志 (flags)。基本模型如下:

    APPNAME VERB NOUN --ADJECTIVE 或者 APPNAME COMMAND ARG --FLAG

    比如:

    hugo server --port=1313
    • hugo:根命令
    • server:子命令
    • –port:标志

    再看个带有参数的例子:

    git clone URL --bare
    git:根命令
    clone:子命令
    URL:参数,即 clone 作用的对象
    –bare:标志

    总结

    • commands 代表行为,是应用的中心点
    • arguments 代表行为作用的对象
    • flags 是行为的修饰符

    1.2 安装

    安装很简单:

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

    但是由于网络原因,有些包会下载失败,提示 i/o timeout

    package golang.org/x/sys/unix: unrecognized import path "golang.org/x/sys/unix" (https fetch: Get https://golang.org/x/sys/unix?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
    package golang.org/x/text/transform: unrecognized import path "golang.org/x/text/transform" (https fetch: Get https://golang.org/x/text/transform?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
    package golang.org/x/text/unicode/norm: unrecognized import path "golang.org/x/text/unicode/norm" (https fetch: Get https://golang.org/x/text/unicode/norm?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)

    推荐使用 gopm 来下载:

    # 下载 gopm,之后会在 $GOPATH/bin 目录下生成 gopm
    go get -u github.com/gpmgo/gop
    # 使用 gopm 来下载 cobra
    gopm get -u -g github.com/spf13/cobra/cobra

    下载完成后安装 cobra 工具,在 $GOPATH/bin 会生成可执行文件:

    go install github.com/spf13/cobra/cobra

    将生成的 cobra 工具放到 $PATH 目录下,可以看到:

    [root@localhost ~]# cp -a $GOPATH/bin/cobra /usr/local/bin
    [root@localhost ~]# cobra
    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.

    接下来我们初始化一个项目。

    1.3 初始化

    通过 cobra init 初始化 demo 项目:

    [root@localhost ~]# cd $GOPATH/src 
    [root@localhost src]# cobra init demo --pkg-name=demo
    Your Cobra applicaton is ready at
    /root/go/src/demo

    当前项目结构为:

    demo
    ├── cmd
    │   └── root.go
    ├── LICENSE
    └── main.go

    可以看到初始化后的项目非常简单,主要是 main.go 和 root.go 文件。在编写代码之前,我们先分析下目前代码的逻辑。

    1.4 代码分析

    先查看下入口文件 main.go。代码逻辑很简单,就是调用 cmd 包里 Execute()函数:

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

    再看下 root.go 中 rootCmd 的字段

    ...

    var rootCmd = &cobra.Command{
    Use: "demo",
    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() {
    if err := rootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
    }
    }

    ...

    简单说明下:

    • Use:命令名
    • Short & Long:帮助信息的文字内容
    • Run:运行命令的逻辑

    Command 结构体中的字段当然远不止这些,受限于篇幅。可以查阅下官方文档。

    运行测试:

    [root@localhost demo]# 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.
    
    subcommand is required
    exit status 1

    2. Cobra 实践
    开始实践。实践环节中,首先提一些需求,然后我们一起实现一个简单的命令行工具。

    2.1 子命令
    之前运行会提示 subcommand is required,是因为根命令无法直接运行。那我们就添加个子命令试试。

    通过 cobra add 添加子命令 create:

    [root@localhost demo]# cobra add create
    create created at /root/go/src/demo

    当前项目结构为:

    demo
    ├── cmd
    │   ├── create.go
    │   └── root.go
    ├── LICENSE
    └── main.go

    查看下 create.goinit() 说明了命令的层级关系:

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

    运行测试:

    # 输入正确
    [root@localhost demo]# go run main.go create
    create called
    
    # 未知命令
    [root@localhost demo]# go run main.go crea
    Error: unknown command "crea" for "demo"
    
    Did you mean this?
        create
    
    Run 'demo --help' for usage.
    unknown command "crea" for "demo"
    
    Did you mean this?
        create

    2.2 子命令嵌套

    对于功能相对复杂的 CLI,通常会通过多级子命令,即:子命令嵌套的方式进行描述,那么该如何实现呢?

    demo create rule

    首先添加子命令 rule :

    [root@localhost demo]# cobra add rule
    rule created at /root/go/src/demo

    当前目录结构如下:

    demo
    ├── cmd
    │   ├── create.go
    │   ├── root.go
    │   └── rule.go
    ├── LICENSE
    └── main.go

    目前create 和 rule 是同级的,所以需要修改 rule.go 的 init() 来改变子命令间的层级关系

    ...
    
    func init() {
            // 修改子命令的层级关系
            //rootCmd.AddCommand(ruleCmd)
            createCmd.AddCommand(ruleCmd)
    }

    虽然调整了命令的层级关系,但是目前运行 demo create 会打印 create called,我希望运行时可以打印帮助提示。所以我们继续完善下代码,修改 create.go

    ...
    
    var createCmd = &cobra.Command{
            Use:   "create",
            Short: "create",
            Long: "Create Command.",
            Run: func(cmd *cobra.Command, args []string) {
                    // 如果 create 命令后没有参数,则提示帮助信息
                    if len(args) == 0 {
                      cmd.Help()
                      return
                    }
            },
    }
    
    ...

    运行测试:

    • 直接运行 create,打印帮助提示:
    [root@localhost demo]# go run main.go create
    Create Command.
    
    Usage:
      demo create [flags]
      demo create [command]
    
    Available Commands:
      rule        A brief description of your command
    
    Flags:
      -h, --help   help for create
    
    Global Flags:
          --config string   config file (default is $HOME/.demo.yaml)
    
    Use "demo create [command] --help" for more information about a command.
    • 运行 create rule,输出 rule called
    [root@localhost demo]# go run main.go create rule
    rule called

    2.3 参数

    现在有个需求:给 CLI 加个位置参数,要求参数有且仅有一个。这个需求我们要如何实现呢?

    demo create rule foo 

    实现前先说下,Command 结构体中有个 Args 的字段,接受类型为 type PositionalArgs func(cmd *Command, args []string) error

    内置的验证方法如下:

    NoArgs:如果有任何参数,命令行将会报错
    ArbitraryArgs: 命令行将会接收任何参数
    OnlyValidArgs: 如果有如何参数不属于 Command 的 ValidArgs 字段,命令行将会报错
    MinimumNArgs(int): 如果参数个数少于 N 个,命令行将会报错
    MaximumNArgs(int): 如果参数个数多于 N 个,命令行将会报错
    ExactArgs(int): 如果参数个数不等于 N 个,命令行将会报错
    RangeArgs(min, max): 如果参数个数不在 min 和 max 之间, 命令行将会报错

    由于需求里要求参数有且仅有一个,想想应该用哪个内置验证方法呢?相信你已经找到了 ExactArgs(int)

    改写下 rule.go:

    ...
    
    var ruleCmd = &cobra.Command{
            Use:   "rule",
            Short: "rule",
            Long: "Rule Command.",
            
            Args: cobra.ExactArgs(1),
            Run: func(cmd *cobra.Command, args []string) {           
              fmt.Printf("Create rule %s success.\n", args[0])
            },
    }
    
    ...

    运行测试:

    • 不输入参数:
    [root@localhost demo]# go run main.go create rule
    Error: accepts 1 arg(s), received 0
    • 输入 1 个参数:
    [root@localhost demo]# go run main.go create rule foo
    Create rule foo success.
    • 输入 2 个参数:
    [root@localhost demo]# go run main.go create rule
    Error: accepts 1 arg(s), received 2

    从测试的情况看,运行的结果符合我们的预期。如果需要对参数进行复杂的验证,还可以自定义 Args,这里就不多做赘述了。

    2.4 标志

    再说说标志。现在要求 CLI 不接受参数,而是通过标志 --name 对 rule 进行描述。这个又该如何实现?

    demo create rule --name foo

    Cobra 中有两种标志:持久标志 ( Persistent Flags ) 本地标志 ( Local Flags )

    持久标志:指所有的 commands 都可以使用该标志。比如:–verbose ,–namespace
    本地标志:指特定的 commands 才可以使用该标志。

    这个标志的作用是修饰和描述 rule的名字,所以选用本地标志。修改 rule.go

    package cmd
    
    import (
            "fmt"        
            "github.com/spf13/cobra"
    )       
    
    // 添加变量 name
    var name string
    
    var ruleCmd = &cobra.Command{
            Use:   "rule",
            Short: "rule",
            Long: "Rule Command.",
            Run: func(cmd *cobra.Command, args []string) {
              // 如果没有输入 name
              if len(name) == 0 {
                cmd.Help()
                return
              }     
              fmt.Printf("Create rule %s success.\n", name)
            },
    }
    
    func init() {
            createCmd.AddCommand(ruleCmd)
            // 添加本地标志
            ruleCmd.Flags().StringVarP(&name, "name", "n", "", "rule name")      
    }

    说明:StringVarP 用来接收类型为字符串变量的标志。相较StringVar, StringVarP 支持标志短写。以我们的 CLI 为例:在指定标志时可以用 --name,也可以使用短写 -n

    运行测试:

    # 这几种写法都可以执行
    [root@localhost demo]# go run main.go create rule -n foo
    Create rule foo success.
    [root@localhost demo]# go run main.go create rule --name foo
    Create rule foo success.
    [root@localhost demo]# go run main.go create -n foo rule
    Create rule foo success.

    2.5 读取配置
    最后说说配置。需求:要求 --name 标志存在默认值,且该值是可配置的

    如果只需要标志提供默认值,我们只需要修改 StringVarP 的 value 参数就可以实现。但是这个需求关键在于标志是可配置的,所以需要借助配置文件。

    很多情况下,CLI 是需要读取配置信息的,比如 kubectl 的~/.kube/config。在帮助提示里可以看到默认的配置文件为 $HOME/.demo.yaml:

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

    配置库我们可以使用 Viper。Viper 是 Cobra 集成的配置文件读取库,支持 YAMLJSON, TOML, HCL 等格式的配置。

    添加配置文件 $HOME/.demo.yaml,增加 name 字段:

    [root@localhost ~]# vim $HOME/.demo.yaml 
    name: foo

    修改 rule.go:

    package cmd
    
    import (
            "fmt"
             // 导入 viper 包
            "github.com/spf13/viper"
            "github.com/spf13/cobra"
    )
    
    var name string
    
    var ruleCmd = &cobra.Command{
            Use:   "rule",
            Short: "rule",
            Long: "Rule Command.",
            Run: func(cmd *cobra.Command, args []string) {
              // 不输入 --name 从配置文件中读取 name
              if len(name) == 0 {
                name = viper.GetString("name")
                // 配置文件中未读取到 name,打印帮助提示
                if len(name) == 0 {
                  cmd.Help()
                  return
                }
              }
              fmt.Printf("Create rule %s success.\n", name)
            },
    }
    
    func init() {
            createCmd.AddCommand(ruleCmd)
            ruleCmd.Flags().StringVarP(&name, "name", "n", "", "rule name")
    }

    运行测试:

    [root@localhost demo]# go run main.go create rule
    Using config file: /root/.demo.yaml
    Create rule foo success.

    如果 CLI 没有用到配置文件,可以在初始化项目的时候关闭 Viper 的选项以减少编译后文件的体积,如下:

    cobra init demo --pkg-name=demo --viper=false

    2.6 编译运行

    ​编译生成命令行工具:

    [root@localhost demo]# go build -o demo

    运行测试:

    [root@localhost demo]# ./demo create rule
    Using config file: /root/.demo.yaml
    Create rule foo success.

    参考文档:

    Golang之使用Cobra : https://o-my-chenjian.com/2017/09/20/Using-Cobra-With-Golang/
    golang命令行库Cobra的使用 : https://www.jianshu.com/p/7abe7cff5384以及基于Cobra的golang命令行工具开发 - 简书 (jianshu.com)
    博客:使用 Cobra 构建命令行工具_Xpitz的博客-CSDN博客

    博客:Go命令行库Cobra的使用_朱小厮的博客-CSDN博客_cobra 子命令

  • 相关阅读:
    D语言中的Range与C#中IEnumreable的区别
    D语言中使用Curl读取网页
    在D语言中如何调用Windows中的COM控件
    D语言反射
    D语言基本类型判断 traits.d
    Struts2 XML验证器
    (十四)Struts2 验证框架
    (十三)Struts2 发送电子邮件
    (十二)Struts2 数据库访问
    (十一)Struts2 文件上传
  • 原文地址:https://www.cnblogs.com/cy0628/p/16095927.html
Copyright © 2020-2023  润新知