文章首发于个人公众号:阿拉平平

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

文章目录

  • 1. Cobra 介绍
    • 1.1 概念
    • 1.2 安装
    • 1.3 初始化
    • 1.4 代码分析
  • 2. Cobra 实践
    • 2.1 子命令
    • 2.2 子命令嵌套
    • 2.3 参数
    • 2.4 标志
    • 2.5 读取配置
    • 2.6 编译运行

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,之后会在 $GOPATH/bin 目录下生成 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 Applicationhelp        Help about any commandinit        Initialize a Cobra ApplicationFlags:-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 mainimport "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?createRun '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 commandFlags:-h, --help   help for createGlobal 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 不接受参数,而是通过标志 --namerule 进行描述。这个又该如何实现?

demo create rule --name foo

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

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

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

package cmdimport ("fmt"        "github.com/spf13/cobra"
)       // 添加变量 name
var name stringvar ruleCmd = &cobra.Command{Use:   "rule",Short: "rule",Long: "Rule Command.",Run: func(cmd *cobra.Command, args []string) {// 如果没有输入 nameif 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 标志存在默认值,且该值是可配置的。

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

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

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

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

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

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

修改 rule.go:

package cmdimport ("fmt"// 导入 viper 包"github.com/spf13/viper""github.com/spf13/cobra"
)var name stringvar ruleCmd = &cobra.Command{Use:   "rule",Short: "rule",Long: "Rule Command.",Run: func(cmd *cobra.Command, args []string) {// 不输入 --name 从配置文件中读取 nameif 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.

参考文档

  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

使用 Cobra 构建命令行工具相关推荐

  1. 使用node.js构建命令行工具

    工具说明 inquirer.js:一个封装了常用命令行交互的node.js模块,通过该模块可以很方便地构建一个新的命令行应用. shell.js:跨平台的unix shell命令模块. Node版本: ...

  2. C#中的样板命令行工具应用程序

    目录 介绍 概念化这个混乱 处理命令行参数 异常处理 用户界面 过期文件处理 编码此混乱 MSBuild支持 用于在C#中构建命令行工具应用程序的入门代码.该样板代码为应用程序提供异常处理和命令行参数 ...

  3. 如何手动写一个命令行工具?

    文章目录 前言 一.一个最简单的命令行工具 二.命令行解析工具 1.commander (1)option (2)version (3)command (4)argument 2.co-prompt ...

  4. Go 通过 cobra 快速构建命令行应用

    这里填写标题 1. Go 通过 cobra 快速构建命令行应用 1.1. cobra 能帮我们做啥? 1.2. cobra 基础使用 1.3. 概念 1.4. Commands 1.5. Flags ...

  5. 酷玩Go命令行工具—Cobra

    不知大家有没有在使用Git命令.Linux的yum命令.Go命令.Maven命令的时候感觉到非常的酷,比如你刚刚拿到一个Go的开源项目,初始化时只需要输入go mod tidy进行对依赖的下载,或者是 ...

  6. 构建现代化的命令行工具

    文章源于 lambdas.dev 每当我们想要创建一个基于 NodeJS 的命令行工具时,就会衍生出一堆问题需要解决,比如如何准备开发环境,如何打包转译代码,如何使代码在转译后保持可调用的状态同时尽可 ...

  7. 【Android 命令行工具】Android 命令行工具简介 ( 官方文档 | SDK 命令行工具 | SDK 构建工具 | SDK 平台工具 | 模拟器工具 | Jetifier 工具 )

    文章目录 一.官方文档 二.Android 命令行工具简介 1.SDK 命令行工具 2.SDK 构建工具 3.SDK 平台工具 4.模拟器工具 5.Jetifier 工具 一.官方文档 Android ...

  8. 代码同步工具_构建现代化的命令行工具

    每当我们想要创建一个基于 NodeJS 的命令行工具时,就会衍生出一堆问题需要解决,比如如何准备开发环境,如何打包转译代码,如何使代码在转译后保持可调用的状态同时尽可能的压缩体积, 以及怎样设计项目分 ...

  9. 使用Cobra开发自己的命令行工具

    Cobra 项目地址:https://github.com/spf13/cobra 1 新建cobra项目 # 安装cobra-cli工具 go install github.com/spf13/co ...

最新文章

  1. 青源Forum | 人工智能的数理基础前沿系列报告 · 第 3 期
  2. BZOJ 4849 [NEERC2016] Mole Tunnels (模拟费用流)
  3. 给定一组查找关键字(19,14,23,1,65,20,84,27,55,11,10,79) 哈希函数为:H(key)=key % 13, 哈希表长为m=15,设每个记录的查找概率相等。【MOOC】
  4. 工作339:pc父组件通过props传值给子组件,如何避免子组件改变props的属性值报错问题
  5. win7系统语音识别操作电脑的操作方法
  6. [转]编程语言与宗教
  7. 从入门到精通,C程序员必读的3本
  8. 建模实训报告总结_建筑实训报告总结
  9. linux在线汇编编译器,Linux 汇编 Hello World
  10. 2.遥感传感器和遥感数据
  11. vue中detele删除对象属性时视图不能响应更新 - 解决办法
  12. elementui实现横向时间轴_element ui step组件在另一侧加时间轴显示
  13. spring quartz 实现全局任务
  14. 杭州烟花爆炸事故无人重伤-游客衣服包裹头逃生-杭州-烟花爆炸-烧伤
  15. IP-Guard十六个模块功能详解
  16. Python 绘制圆锥体(3D图)
  17. 软件测试 三角形问题
  18. linux搭建网站教程详解
  19. 狼人杀3.0版本(自创)
  20. C/C++语言100题练习计划 94——矩阵转置(线性代数)

热门文章

  1. 2021中山大学计算机考研复试与调剂经验
  2. Python读取文件中文乱码问题
  3. c++类内的static变量初始化和static函数
  4. c# 数据绑定之 DataFormatString 格式
  5. Java并发之Semaphore源码解析
  6. Python 线上部署流程
  7. 计算机上怎么用函数算比例,如何使用Excel函数计算所占的比例
  8. IDEA启动报Failed to create JVM. JVM Path错问题解决
  9. 关于java开发银行业务_一文教你使用java开发银行柜员业务绩效考核系统
  10. File Zilla服务器回应不可路由的地址。被动模式失败。 -- 解决方案