golang常用库:cli命令行/应用程序生成工具-cobra使用
我前面有一篇文章介绍了配置文件解析库 Viper 的使用,这篇介绍 Cobra 的使用,你猜的没错,这 2 个库都是同一个作者 spf13,他开发了很多与 golang 相关的库,他目前在 google 领导着 golang 产品相关开发工作。
Cobra 是关于 golang 的一个命令行解析库,用它能够快速创建功能强大的 cli 应用程序和命令行工具。
它被很多知名的项目使用,比如 Kubernetes,Github CLI,Etcd 等。更多应用此库的项目列表。
我们平常用到命令:git commit -m "message",docker containter start 等都可以用 cobra 来实现。
Cobra 相关文档地址:
Cobra 的logo:
(from:https://github.com/spf13/cobra)
更多功能特性请查看: cobra文档介绍
Cobra 命令结构由3部分组成:
commands、arguments 和 flags
commands:
命令行,代表行为动作,要执行的一个动作。每个命令还可以包含子命令。分为:rootCmd 和 subCmd。程序中具体对象是 cobra.Command{},这个是根命令;子命令(subCmd)用 rootCmd.AddCommand() 添加,子命令通常也会单独存一个文件,
并通过一个全局变量让 rootCmd 可以 add 它。
arguments:
命令行参数,通常是 []string 表示
flags:
命令行选项。对 command 进一步的控制。通常用一短横 -
或者两短横 --
标识。程序中读取存储在变量中。
cobra 命令行格式:
APPNAME VERB NOUN --ADJECTIVE APPNEM COMMAND ARG --FLAG
例子说明:
hugo server --port=1313 #server 代表 command, port 代表 flag。 git clone URL --bare #clone 代表 command,URL 代表操作的物-argument,bare 代表 flag。
golang v1.15, cobra v1.2.1
安装 cobra:
go get -u github.com/spf13/cobra
可以用 cobra -h
来查看 cobra 命令的一些用法。
Usage: cobra [command] Available Commands: add Add a command to a Cobra Application completion generate the autocompletion script for the specified shell help Help about any command init Initialize a Cobra Application
安装 cobra generator:go get -u github.com/spf13/cobra/cobra
使用命令 cobra init 来创建第一个应用程序,这个命令也是初始化一个应用程序的项目框架:
cobra init firstappname Error: required flag(s) "pkg-name" not set
报错了,错误信息截图如下:
错误信息:需要设置 --pkg-name 参数。
因为我们项目不存在。先创建名为 firstappname 文件夹,然后进入目录 firstappname,在命令行下运行:cobra init --pkg-name firstappname
。
自动生成了如下目录和程序:
下面程序我去掉了英文注释部分。
main.go
package main import "firstappname/cmd" func main() { cmd.Execute() }
cmd/root.go
package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper" ) var cfgFile string // 构建根 command 命令。前面我们介绍它还可以有子命令,这个command里没有构建子命令 var rootCmd = &cobra.Command{ Use: "firstappname", 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.` } // 执行 rootCmd 命令并检测错误 func Execute() { cobra.CheckErr(rootCmd.Execute()) } func init() { // 加载运行初始化配置 cobra.OnInitialize(initConfig) // rootCmd,命令行下读取配置文件,持久化的 flag,全局的配置文件 rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.firstappname.yaml)") // local flag,本地化的配置 rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } // 初始化配置的一些设置 func initConfig() { if cfgFile != "" { viper.SetConfigFile(cfgFile) // viper 设置配置文件 } else {// 上面没有指定配置文件,下面就读取 home 下的 .firstappname.yaml文件 // 配置文件参数设置 home, err := os.UserHomeDir() cobra.CheckErr(err) viper.AddConfigPath(home) viper.SetConfigType("yaml") viper.SetConfigName(".firstappname") } viper.AutomaticEnv() if err := viper.ReadInConfig(); err == nil {// 读取配置文件 fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } }
其实上面的错误在 cobra generator 文档里有提示了,所以要多看官方文档。
这个 root.go 里的 cobra.Command 就是设置命令行格式的地方。如果要执行相关的命令,可以在 Long:
...下面加一行:
Run: func(cmd *cobra.Command, args []string) { },
运行程序:go run main.go , 报错了:
我用的是 go v1.15,GO111MODULE="on"。
用 go mod 来创建 module,进入firstappname目录,命令: go mod init firstappname
,生成一个 go.mod,
module firstappname go 1.15 require ( github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.9.0 )
在运行 go run main.go,第一次运行会下载文件到 go.mod, go.sum 里。再次运行,就会出现 rootCmd 下的 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.
可以看出,用 cobra init 命令初始化的项目, 生成了一个初始化的应用框架,但是没有任何逻辑功能。仅仅输出一些描述性信息。
这个程序里,最重要的是 cmd/root.go 里的 rootCmd = &cobra.Command{} 这行程序,这里定义命令动作。
程序里的 init() 和 initConfig() 函数都是对命令行的配置。
为 rootCmd 添加功能:
var rootCmd = &cobra.Command{ Use: "firstappname", Short: "A brief description of your application", Long: `(root)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) { fmt.Println("root called") }, }
测试运行 go run main.go
,输出:
$ go run main.go root called
运行:go run main.go --help
会输出上面的 Long 信息和完整的帮助信息。
也可以把上面命令编译:go build -o demo.exe,在运行
完整生成一个cobra应用框架的命令:
$ mkdir firstappname $ cd firstappname $ cobra init --pkg-name firstappname $ go mod init firstappname
上面我们用 cobra init
创建了应用程序框架,在程序 cmd/root.go 里有一个根命令 rootCmd,也就是说 init 命令创建了一个根命令。执行 command 命令是 &cobra.Command{} 里的 Run 方法。
用 cobra add
来为 rootCmd 创建一个子命令。这个子命令通常在一个单独的文件里。
// cd 进入firstappname $ cd ./firstappname $ cobra add demo demo created at D:\work\mygo\common_pkg\cobra\firstappname
在 cmd 目录下生成了 demo.go 文件:
为子命令添加简单功能
add 命令已经为我们生成了一个简单的应用代码,程序文件通常存放在cmd目录下,demo.go 程序:
package cmd import ( "fmt" "github.com/spf13/cobra" ) // demoCmd represents the demo command // 子命令 var demoCmd = &cobra.Command{ Use: "demo", 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("demo called") fmt.Println("cmd demo") }, } func init() { rootCmd.AddCommand(demoCmd) }
到现在为止,为 firstappdemo 添加了 2 个 Command 了,分别是根命令 rootCmd 和子命令 demoCmd。
子命令和根命令的关系一般通过程序 rootCmd.AddCommand()
方法确定。在程序 demo.go 里可以看到它在 init() 函数里。
Run 方法里添加程序:fmt.Println("cmd demo")。一般这里的程序都是其他 package 里完成了具体逻辑,然后 Run 方法里在调用这些程序。
测试运行:go run main.go demo
输出:
demo called
cmd demo
也可以编译项目 go build -o xxx 在运行。
flag 命令行选项,也叫标识,对command命令行为的进一步指示操作。
用这个标识可以给 command 添加一些可选项。
根据 flag 选项作用范围不同,可以分为 2 类:
Persistent Flags,持久化的flag,全局范围
如果设置全局范围的flag,可以用这个来设置。它既可以给根命令设置选项值,也可以给子命令设置选项值。
下面例子里的 rootCmd 和 demoCmd 都可以调用 flag。
Local Flags,局部flag,只对指定的command生效。比如某个子命令的 flag。
因为 flag 标识是在命令行后面不同位置使用,所以我们要在方法外定义一个变量,来分配存储使用这个标识符。下面例子会说明。
在 cmd/root.go 文件中添加一个变量 name
var cfgFile string // 添加 name var name string
然后在 root.go:init() 函数中添加全局 persistent flag,把 flag 值存储到变量 name 中
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.firstappname.yaml)") // 添加全局 flag rootCmd.PersistentFlags().StringVar(&name, "name", "", "Set one name")
在文件 cmd/demo.go 中的 demoCmd(子命令) 里 Run 方法输出 name 值
Run: func(cmd *cobra.Command, args []string) { fmt.Println("demo called") fmt.Println("cmd demo") // 打印输出 name fmt.Println("print persistent flag name: ", name) },
测试运行程序
$ go run main.go demo --name setname demo called cmd demo print persistent flag name: setname
当然也可以先编译 go build -o cobrademo.exe(我用的win),然后在运行测试程序
$.\cobrademo.exe demo --name setname1 demo called cmd demo print persistent flag name: setname1
Persistent flag 的读取方法:
// arg1:存储变量, // arg2:设置长flag名,这里 name 显示 --name, // arg3:设置短flag名,这里 n 显示 -n,一般与上面对应 // arg4:默认值, 这里设置为 "" // arg5:flag的一些说明信息 PersistentFlags().StringVarP(&name, "name", "n", "", "Set one name") // 与上面用法基本相同,只是没有短 flag 设置 PersistentFlags().StringVar(&name, "name", "", "Set one name") // 直接设置flag名,arg1:flag 名,arg2:默认值,arg3:说明 PersistentFlags().String("foo", "", "A help for foo")
一个 flag 赋值给本地变量,只能对指定的command生效。
我们在 demo.go 中测试 local flag。
// 定义 local flag var dsn string
在 demo.go 中的 init() 中添加下面代码,把值存储到 dsn上
demoCmd.Flags().StringVarP(&dsn, "dsn", "d", "", "dsn file")
在 demoCmd.Command{} 获取该值
Run: func(cmd *cobra.Command, args []string) { fmt.Println("demo called") fmt.Println("cmd demo") // 打印输出 name fmt.Println("print persistent flag name: ", name) // 打印输出local flag: dsn fmt.Println("(local flag)print dsn: ", dsn) },
测试运行
$ go run .\main.go demo --dsn setdsn1 demo called cmd demo print persistent flag name: (local flag)print dsn: setdsn1
输出了 setdsn1。
测试下其它子命令可以不可以获取这个 dsn,添加一个新的子命令 demo2,
$ cobra add demo2
在目录 cmd 下添加了文件 demo2.go, 在 Run 下添加:
Run: func(cmd *cobra.Command, args []string) { fmt.Println("demo2 called") // 添加输出 dsn fmt.Println("test get local flag(dsn): ", dsn) },
测试:
$ go run .\main.go demo2 --dsn testdsn Error: unknown flag: --dsn
报错了,程序终止运行了。
说明:local flag 局部选项,只能作用于指定的 command。本例子中作用于 demoCmd,而不能作用于 demo2Cmd。
local flag 的读取方法:
// arg1:存储变量, // arg2:设置长flag名,这里 name 显示 --name, // arg3:设置短flag名,这里 n 显示 -n,一般与上面对应 // arg4:默认值, 这里设置为 "" // arg5:flag的一些说明信息 // 方法(1) Flags().StringVarP(&name, "name", "n", "", "Set one name") // 与上面方法(1)用法基本相同,只是没有短 flag 设置 Flags().StringVar(&name, "name", "", "Set one name") // 直接设置flag名,arg1:flag 名,arg2:默认值,arg3:说明 Flags().String("foo", "", "A help for foo") // 与上面方法(1)用法基本相同,除了第一个没有变量读取参数 Flags().StringP("toggle", "t", false, "Help message for toggle")
完整例子在 github 上,golang-library-learning/cobra
比如给 demo.go 的 dsn 这个 flag 设置必选项
demoCmd.Flags().StringVarP(&dsn, "dsn", "d", "", "dsn file") // 把 dsn 设置为必选项 demoCmd.MarkFlagRequired("dsn")
flag 不设置dsn,运行程序:go run main.go demo
, 报错:Error: required flag(s) "dsn" not set
$ go run .\main.go demo Error: required flag(s) "dsn" not set Usage: firstappname demo [flags] Flags: -d, --dsn string dsn file -h, --help help for demo Global Flags: --config string config file (default is $HOME/.firstappname.yaml) --name string Set one name Error: required flag(s) "dsn" not set exit status 1
加上 dsn 运行,go run main.go demo --dsn setdsn
,正常输出:
$ go run main.go demo --dsn setdsn demo called cmd demo print persistent flag name: (local flag)print dsn: setdsn
还可以绑定配置到 flags 上,用 viper
在 cmd/root.go 里,有一个 initConfig()
方法,这个就是初始化配置方法。加载执行是在 init()
方法里,
func init() { cobra.OnInitialize(initConfig) ... ... }
我们可以在 init() 方法中添加绑定 flag 程序,
rootCmd.PersistentFlags().StringVar(&name, "name", "", "Set one name") viper.BindPFlag("name", rootCmd.PersistentFlags().Lookup("name"))
这样就将 viper 配置和 flag 绑定,如果用户不设置 --name,将从配置中查找。
更多方法请查看 viper flag doc
可以用Command 的 Args 字段指定参数效验规则。
Cobra 也内置了一些规则:
NoArgs:如果有任何命令行args参数,将会报错
ArbitraryArgs:该命令接受任何参数
OnlyValidArgs:如果该命令参数不在 Command 的 ValidArgs 中,将会报错
MinimumArgs(int): 如果命令参数数目少于N个,将会报错
MaximumArgs(int): 如果命令参数数目多于N个,将会报错
ExactArgs(int): 如果命令参数数目不是N个,将会报错
RangeArgs(min, max):如果命令参数数目范围不在(min, max),将会报错
内置效验规则的例子:
var rootCmd = &cobra.Command{ Use: "dmeo", Short: "demo short", Long: `let's do it, demo!`, Args: cobra.MinimumNArgs(5), Run: func(cmd *cobra.Command, args []string) { fmt.Println("hello chenqionghe") }, }
自定义验证规则的例子:
var cmd = &cobra.Command { Short: "demo", Args: func(cmd *cobra.Command, args[] string) error { if len(args) > 0 { return errors.New("requires a color argument") } 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, demo!") }, }
可以在执行命令之前或之后运行钩子函数。如果子命令未声明自己的 Persistent * Run
函数,则子命令将继承父命令的钩子函数。
函数的执行顺序为:
package main import ( "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) }, } 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() }
运行程序:
$ go run .\hookdemo.go 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]
Cobra 可以基于子命令、标志等生成文档。具体的使用方法和生产格式文档请点击下面链接:
你可以设置 cmd.DisableAutoGenTag = true
从而把文档中 "Auto generated by spf13/cobra..." 等字样删掉。
命令: cobra help
,可以清楚显示出对使用 cobra 有用的信息,比如命令提示
你还可以定义自己的 help 命令或模板
cmd.SetHelpCommand(cmd *Command) cmd.setHelpCommand(f func(*Command, []string)) cmd.setHelpTemplate(s string)
cobra github
cobra user guide
Cobra Generator