Go应用构建工具(3)–cobra

1. 概述

Cobra是一个可以创建强大的现代化命令行应用的库,许多应用都使用了Cobra库构建应用,比如k8s,Hugo等;
Cobra同时也提供了一个脚手架,用来生成基于Cobra的应用框架。
Cobra是建立在commands,arguments和flags的结构上的,commands代表命令,args代表非选项参数,flags代表标志。
一个好的应用应该是易懂的,用户能够直观的与其交互,应用通常遵循这种模式:APPNAME VERB NOUN --ADJECTIVE或者APPNAME COMMAND ARG --FLAG
比如以下示例:

hugo server --port=1313git clone URL --bare

这里VERB表示动词,NOUN表示名词,ADJECTIVE表示形容词

Cobra的使用提供了两种方式:使用Cobra库或者cobra-cli脚手架
两种方式都需要先安装:

  • cobra库: go install github.com/spf13/cobra/cobra@latest
  • cobra-cli:go install github.com/spf13/cobra-cli@latest

2. 快速使用

  1. 通用结构
    通常基于cobra的应用程序遵循以下的组织结构:
  ▾ appName/▾ cmd/add.goyour.gocommands.gohere.gomain.go

其中main.go文件是很简洁的,它只做一件事,就是初始化cobra:

package mainimport ("{pathToYourApp}/cmd"
)func main() {cmd.Execute()
}
  1. 快速使用
  • 首先按照上面的组织结构创建自己的项目
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PKTjwsCz-1675062169702)(./img/Go应用构建工具%283%29–cobra.md/img-20230116105955.png)]

  • 在cmd/root.go添加以下代码

package cmdimport ("fmt""os""github.com/spf13/cobra"
)var rootCmd = &cobra.Command{Use:   "mycli",Short: "mycli is a cobra test code",Long:  "mycli is a cobra test code,this is long description",
}func Execute() {if err := rootCmd.Execute(); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)
  • 添加main.go
package mainimport "cobrafast/cmd"func main() {cmd.Execute()
}
  • 添加另外的命令,这里加一个version,在version.go写入以下代码:
package cmdimport ("fmt""github.com/spf13/cobra"
)var versionCmd = &cobra.Command{Use:   "version",Short: "Print the version number of mycli",Long:  "All software has versions. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Println("V1.0.01")},
}func init() {rootCmd.AddCommand(versionCmd)
}

每个cobra都有一个根命令,然后它下面有很多子命令,这里的version就是其中一个子命令,使用AddCommand添加到根明了中

  • 测试验证
  1. cobra自动生成帮助信息
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go -h
mycli is a cobra test code,this is long descriptionUsage:mycli [flags]mycli [command]Available Commands:completion  Generate the autocompletion script for the specified shellhelp        Help about any commandversion     Print the version number of mycliFlags:-h, --help   help for mycliUse "mycli [command] --help" for more information about a command.

单个命令的帮助信息也自动生成了:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go version -h
All software has versions. This is mycli'sUsage:mycli version [flags]Flags:-h, --help   help for version

运行子命令:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$  go run main.go version
V1.0.01

运行未定义的命令:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go get
Error: unknown command "get" for "mycli"
Run 'mycli --help' for usage.
unknown command "get" for "mycli"
exit status 1
  1. 如果希望调用命令时将错误返回,可以使用RunE方法,错误信息会被Execute方法捕获
    这里将version.go的versionCmd的Run字段改为RunE:
var versionCmd = &cobra.Command{Use:   "version",Short: "Print the version number of mycli",Long:  "All software has versions. This is mycli's",RunE: func(cmd *cobra.Command, args []string) error {// fmt.Println("V1.0.01")return errors.New("version error")},
}

运行子命令:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go version
Error: version error
Usage:mycli version [flags]Flags:-h, --help   help for versionversion error
exit status 1

3. 核心特性

3.1 使用flag

Cobra使用pflag解析命令行选项,Cobra中有两种类型的flag:一种是持久化的flag,一种是本地flag。

  • 持久化flag
    定义它的命令和其子命令都可以使用这个flag。
  • 本地flag
    只能在定义它的命令中使用

以下用示例来学习这个特性:
root.go新增以下代码:

var output stringfunc init() {// 根命令定义一个持久化flagrootCmd.PersistentFlags().StringVarP(&output, "output", "o", "", "output file name")
}

output这个flag是持久化的,子命令version可以使用

var versionCmd = &cobra.Command{Use:   "version",Short: "Print the version number of mycli",Long:  "All software has versions. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Printf("version: V1.0.01, output: %s\n", output)},
}

测试:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go version -o test.txt
version: V1.0.01, output: test.txt
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go version -h
All software has versions. This is mycli'sUsage:mycli version [flags]Flags:-h, --help   help for versionGlobal Flags:-o, --output string   output file name

通过-h查看帮助信息能看到,version命令多了一个Global Flags
接下来,将root.go的持久化flag改为本地flag:

func init() {// 根命令定义一个本地flagrootCmd.Flags().StringVarP(&output, "output", "o", "", "output file name")
}

此时再查看version命令的帮助信息,就会发现没有output这个flag了:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go version -h
All software has versions. This is mycli'sUsage:mycli version [flags]Flags:-h, --help   help for version
  • cobra还提供了一种在解析子命令前能够先解析父命令的本地flag的方式
    默认情况是本地flag只能在定义它的命令上使用,但可以通过设置字段TraverseChildren,让cobra在执行目标命令先解析父命令的本地flag。
    文字描述的有点拗口,官方文档说明也比较简短,下面通过实验来说明会比较容易明白。
  1. 首先修改下代码,新增一个get命令,version为它的子命令,get命令定义一个本地flag
package cmdimport ("fmt""github.com/spf13/cobra"
)var output stringvar getCmd = &cobra.Command{Use:              "get",Short:            "Print output file name of mycli",Long:             "All software has name. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Printf("name: %s\n", output)},
}func init() {getCmd.Flags().StringVarP(&output, "output", "o", "mycli", "output file name")rootCmd.AddCommand(getCmd)
}
  1. version命令代码,将version命令挂在get命令下:
var versionCmd = &cobra.Command{Use:   "version",Short: "Print the version number of mycli",Long:  "All software has versions. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Printf("version: V1.0.01, output: %s\n", output)},
}
func init() {getCmd.AddCommand(versionCmd)
}
  1. 先测试下正常情况
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get version -o hello
Error: unknown shorthand flag: 'o' in -o
Usage:mycli get version [flags]Flags:-h, --help   help for versionunknown shorthand flag: 'o' in -o
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get -o hello version
Error: unknown shorthand flag: 'o' in -o
Usage:mycli get version [flags]Flags:-h, --help   help for versionunknown shorthand flag: 'o' in -o

常规情况下,-o是父命令get的本地flag,子命令version无法使用

  1. 修改代码(这里搞不懂跟我理解的不同,先看看代码)
    修改root.go,将TraverseChildren设置为true
var rootCmd = &cobra.Command{Use:              "mycli",Short:            "mycli is a cobra test code",Long:             "mycli is a cobra test code,this is long description",TraverseChildren: true,
}

然后再测试:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go build .
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get  -o hello version
version: V1.0.01, output: hello
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get version -o hello
Error: unknown shorthand flag: 'o' in -o
Usage:mycli get version [flags]Flags:-h, --help   help for versionunknown shorthand flag: 'o' in -o

这时,使用命令如./cobrafast get -o hello version,version命令就能先解析了get命令的本地flag了。
::: danger 注意
在这一块,我有几个点不是很能理解~

  1. 按照我的理解,应该是子命令里将TraverseChildren设置为true后,执行该命令时能解析父命令的flag才是,但实质上并不是,而是在root命令里添加;
  2. 基于上一点的问题,查看源码中判断TraverseChildren的地方,是在方法:ExecuteC中,有以下代码:
var flags []stringif c.TraverseChildren {cmd, flags, err = c.Traverse(args)} else {cmd, flags, err = c.Find(args)}

这个方法的调用链是这样的:Execute()-> ExecuteC(),因为我们只在root.go中调用了Execute()方法,因而才需要在rootCmd中添加

func Execute() {if err := rootCmd.Execute(); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)}
}

也许是我个人理解的不对,希望有知道的朋友能告诉我哈
3. 对于命令的执行我也是有点不理解,我以为是可以将-o写在version命令之后,实则是不行,必须要在父命令后跟上父命令的本地flag,在写上子命令才可以成功执行。
:::

3.2 将flag绑定到配置

cobra使用的配置包是viper,我们可以将flag绑定到viper,这一部分与在viper中绑定flag是一样的了。

  1. 首先新建一个配置文件,config/config.yaml
the_author: zhangsan
  1. 修改root.go,添加viper的初始化
func initViper() {viper.SetConfigName("config")viper.SetConfigType("yaml")viper.AddConfigPath("./config")if err := viper.ReadInConfig(); err != nil {fmt.Fprintln(os.Stderr, err)os.Exit(1)}
}
func init() {cobra.OnInitialize(initViper)
}
  1. 在get命令中绑定flag到viper,在执行命令时打印
var output string
var author stringvar getCmd = &cobra.Command{Use:   "get",Short: "Print output file name of mycli",Long:  "All software has name. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Printf("name: %s, author: %s\n", output, viper.GetString("the_author"))},
}func init() {getCmd.Flags().StringVarP(&output, "output", "o", "mycli", "output file name")getCmd.PersistentFlags().StringVar(&author, "author", "lucas", "Author name for copyright attribution")viper.BindPFlag("the_author", getCmd.PersistentFlags().Lookup("author"))rootCmd.AddCommand(getCmd)
}
  1. 运行测试
  • 没有指定flag时,读取的是配置文件的数据
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get
name: mycli, author: zhangsan
  • 指定–author时,读取的是命令行的值
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get --author lisi
name: mycli, author: lisi
  • 将配置文件的数据注释,也不指定–author,读取的就是定义flag设置的默认值了
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get
name: mycli, author: lucas

3.3 设置flag为必选

默认情况下,flag是可选的,如果希望在某个flag没有被设置时候报错,则可以将它标记为必选的,使用MarkFlagRequired()MarkPersistentFlagRequired()
比如将output标记为必选的:

func init() {getCmd.Flags().StringVarP(&output, "output", "o", "mycli", "output file name")getCmd.PersistentFlags().StringVar(&author, "author", "lucas", "Author name for copyright attribution")getCmd.MarkFlagRequired("output")// getCmd.MarkPersistentFlagRequired("author")viper.BindPFlag("the_author", getCmd.PersistentFlags().Lookup("author"))rootCmd.AddCommand(getCmd)
}

那么在运行时,没有指定–output或-o,则会报错:

lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get
Error: required flag(s) "output" not set
Usage:mycli get [flags]mycli get [command]Available Commands:version     Print the version number of mycliFlags:--author string   Author name for copyright attribution (default "lucas")-h, --help            help for get-o, --output string   output file name (default "mycli")Use "mycli get [command] --help" for more information about a command.required flag(s) "output" not set

3.4 flags组

  • 如果有不同的标志,必须一起提供(例如,–username标志,必须提供–password标志),那么Cobra可以强制要求,使用MarkFlagsRequiredTogether()
  • 如果有不同的标志,是互斥的,不能同时提供,比如输出格式只能是–json或–yaml,不能同时设置的情况,可以使用MarkFlagsMutuallyExclusive()

对于这两种情况:

  • 所有的本地flag和持久化flag都可以使用
  • 一个flag可以出现在多个组中
  • 一个组可以包含任意个flag

3.5 位置参数和自定义参数

在命令中,除了flag标志外,通常也会有参数Arg,并且有时需要对这些参数进行验证,cobra提供了一些内置的验证函数:

  1. NoArgs: 如果存在任何的参数,将会报错
  2. ArbitaryArgs:接受任意参数
  3. MinimumNArgs(int):接受至少N个参数,否则报错
  4. MaximumNArgs(int):接受至多N个参数,否则报错
  5. ExactArgs(int):只接收N个参数,如果个数不对,报错
  6. RangeArgs(min, max):参数个数在min和max之间,否则报错
  7. OnlyValidArgs:如果没有一个参数是符合command的ValidArgs字段指定的参数的话,就报错;这个要配合ValidArgs使用
  8. MatchAll:这个方法可以组合上面几种验证函数,比如可以要求参数个数必须为2个,而且需要满足指定的参数
var getCmd = &cobra.Command{Use:   "get",Short: "Print output file name of mycli",Long:  "All software has name. This is mycli's",Run: func(cmd *cobra.Command, args []string) {fmt.Printf("name: %s, author: %s\n", output, viper.GetString("the_author"))},ValidArgs: []string{"hello", "word"},Args:      cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs),
}
  1. 也可以自定义验证函数,实现方法:func(cmd *cobra.Command, args []string) error
    比如:
var cmd = &cobra.Command{Short: "hello",Args: func(cmd *cobra.Command, args []string) error {// Optionally run one of the validators provided by cobraif err := cobra.MinimumNArgs(1)(cmd, args); err != nil {return err}// Run the custom validation logicif 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!")},
}

3.6 钩子函数:PreRun和PostRun

cobra提供了一些钩子函数,在运行命令的Run函数时,PersistentPreRunPreRun函数会在Run函数执行之前执行,PersistentPostRunPostRun函数会在Run函数执行之后执行。
对于带有Persistent的函数,如果它的子命令没有实现这个函数,那么子命令将会继承父命令的Persistent*Run方法
这些钩子函数的执行顺序如下:

  1. PersistentPreRun
  2. PreRun
  3. Run
  4. PostRun
  5. PersistentPostRun
package mainimport ("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]

3.7 其他特性

cobra还支持其他比较有用的特性,比如:自定义Help命令,自定义帮助信息,添加–version标志,输入无效命令时的建议等,还可以生成命令的相关文档…

Go应用构建工具(3)--cobra相关推荐

  1. Go: 常用工具库cobra的简介与实践

    文章目录 简介 一.功能介绍 二.基础使用 三.简单实用 小结 参考地址 简介 来自jetbrains Go 语言现状调查报告 显示:在go开发者中使用go开发实用小程序的比例为31%仅次于web,g ...

  2. linux java 构建工具有哪些,Linux ant --强大的Java开发构建工具

    用途说明 ant严格说来,ant其实并非原生的Linux命令,但它是一个使用广泛.功能强大的跨平台构建工具程序,尤其是进行Java开发时,许多开源的Java项目都使用ant作为构建工具.ant命令一般 ...

  3. scala构建工具sbt使用介绍

    sbt工具下载及说明: https://www.scala-sbt.org/0.13/docs/zh-cn/Installing-sbt-on-Windows.html sbt是交互式构建工具,使用s ...

  4. iOS应用模块化的思考及落地方案(二)模块化自动构建工具的使用

    1.0 iOS模块化中的问题 前文已经介绍了模块化的流程及一些常见的问题,我们在这里再次总结一下. 在工作中,当我们开始一个新项目的时候,最先考虑的就是模块化工作. 模块化工作的想法是很美好的,可是执 ...

  5. Maven官宣:干掉Maven和Gradle!推出更强更快更牛逼的新一代构建工具,炸裂!

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:网络 相信作为Java开发者的你早已经受够了maven的编译缓慢,但是又由于历史包袱.使用习惯等问题暂时切换不了其他更快的构建 ...

  6. 下一代构建工具 Gradle ,比 Maven 强在哪里!

    作者 :乐百川 本文:toutiao.com/i6824937779193971207 相信使用Java的同学都用过Maven,这是一个非常经典好用的项目构建工具.但是如果你经常使用Maven,可能会 ...

  7. 55 前端构建工具Gulp

    技术交流QQ群:1027579432,欢迎你的加入! 欢迎关注我的微信公众号:CurryCoder的程序人生 1.第三方模块Gulp Gulp:基于node平台开发的前端构建工具. 前端构建工具:将机 ...

  8. java+构建+工具+Ant+Maven+Gradle

    java+构建+工具+Ant+Maven+Gradle Ant+Maven+Gradle+............ 目前: Ant已经销声匿迹.Maven也没落了,而Gradle的发展则如日中天. M ...

  9. 构建工具Gradle

    1.Summary   从Android团队开始宣布放弃Eclipse转投Android Studio时,构建工具Gradle进入了Android开发者的视野.而随着热修复.插件化.编译时注解的流行, ...

最新文章

  1. 浅析Spring——控制反转IoC
  2. VR变革已来!华为完成业界首个5G实验网下Cloud VR业务验证
  3. pytorch maxpool和卷积尺寸问题
  4. python语言实例-Python与其他语言比较实例
  5. 笔记 - Ali Cloud 块存储简介
  6. Hadoop生态hive(三)Hive QL介绍
  7. c++中的异常---2(异常接口声明,异常变量的生命周期,异常的多态使用)
  8. IOC操作Bean管理XML方式(注入外部bean)
  9. pyecharts anaconda_Pyecharts安装使用和绘图案例
  10. 废弃fastjson!大型项目迁移Gson保姆级实战
  11. 前端错误日志上报相关实践
  12. 重定向拼接中文参数和特殊字符
  13. Dynamics CRM 2013 installation
  14. 芯片之路: 海思半导体前世今生
  15. FC1179U盘量产教程
  16. 三维激光扫描后处理软件_地面三维激光扫描仪应用之一|云尚智造
  17. 您未被授权查看该页 的解决办法。
  18. 是java运行时环境的缩写,java运行环境的英文缩写
  19. unity cardboard 导出
  20. 【Web技术】985- 当聊到前端性能优化时,我们会关注什么?

热门文章

  1. Ps图层工具怎么使用?以下是我分享的关于ps图层几个小知识
  2. 密码管理神器-1Password,安全与否?
  3. 打单工具有哪些?拼多多商家如何打单?
  4. centos7配置端口转发
  5. STM32驱动ADXL345三轴传感器
  6. shell之读取/etc/passwd中user及其id
  7. YOLOv5改进之八:非极大值抑制NMS算法改进Soft-nms
  8. STL——STL简介、STL六大组件
  9. 计算机网络实验一 网络命令
  10. 考研线性代数手写笔记3 向量