Golang | 使用 Cobra 构建命令行工具


阿拉平平

读完需要

15

分钟

速读仅需5分钟

最近折腾了下命令行库 Cobra,和大家分享下。本文演示环境为 CentOS 7.5,Golang 1.11。

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 是行为的修饰符

相信看了例子后,应该有个直观的认识了。接下来我们安装 Cobra。


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
go get -u github.com/gpmgo/gopm

# 使用 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.goroot.go 文件在编写代码之前,我们先分析下目前代码的逻辑。


1.4 代码分析


   

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

import "demo/cmd"

func main() {
  cmd.Execute()
}

再看下 root.gorootCmd 的字段:

...

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

目前 createrule 是同级的,所以需要修改 rule.goinit() 来改变子命令间的层级关系:

...

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

[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 不接受参数,而是通过标志 –namerule 进行描述。这个又该如何实现?
demo create rule --name foo

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

  • 持久标志:指所有的命令都可以使用该标志。比如:–verbose ,–namespace

  • 本地标志:指特定的命令才可以使用该标志。

这个标志的作用是修饰和描述 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 用来接收类型为字符串变量的标志。相较 StringVarStringVarP 支持标志短写。以我们的 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, TOMLHCL 等格式的配置。

添加配置文件 $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.


References

1. Github :https://github.com/spf13/cobra
2.  Cobra 的一些笔记:https://zhangguanzhang.github.io/2019/06/02/cobra/
3. Golang之使用 Cobra:
https://o-my-chenjian.com/2017/09/20/Using-Cobra-With-Golang/

4. golang命令行库Cobra的使用:https://www.jianshu.com/p/7abe7cff5384




原文始发于微信公众号(阿拉平平):Golang | 使用 Cobra 构建命令行工具

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/288084.html

(0)
码上实战的头像码上实战

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!