go语言命令行工具cobra
1、Cobra 介绍
Cobra 是关于 golang 的一个命令行解析库,用它能够快速创建功能强大的 cli 应用程序和命令行工具。
cobra既是一个用于创建强大现代CLI应用程序的库,也是一个生成应用程序和命令文件的程序。cobra被用在很多
go语言的项目中,比如 Kubernetes、Docker、Istio、ETCD、Hugo、Github CLI等等。
我们平常用到命令:git commit -m “message”,docker containter start 等都可以用 cobra 来实现。
Cobra 官网:https://cobra.dev/
github地址:https://github.com/spf13/cobra
2、功能特性介绍
- 很多子命令的CLIS: 比如 app server、app fetch 等
- 支持嵌套子命令(sub-command)
- 轻松完成应用程序和命令的创建:cobra init appname 和 cobra add cmdname
- 为应用程序生成 man 手册
- 全局、本地和级联 flag
- 为 shell 程序完成自动提示(bash,zsh,fish, powershell etc.)
- 支持命令行别名,可以帮助你更容易更改内容而不破坏他们
- 灵活定义自己的help、usage信息
- 可选集成 viper 配置管理工具库
3、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
例子说明:
# server代表command,port代表flag
hugo server --port=1313
# clone代表command,URL代表argument,bare代表flag
git clone URL --bare
4、Cobra基本使用方法
安装 cobra:
$ go get -u github.com/spf13/cobra
安装 cobra-cli:
$ go get github.com/spf13/cobra-cli
$ go install github.com/spf13/cobra-cli
可以用 cobra-cli -h
来查看 cobra 命令的一些用法。
$ cobra-cli -h
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-cli [command]Available Commands:add Add a command to a Cobra Applicationcompletion Generate the autocompletion script for the specified shellhelp 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-cli-l, --license string name of license for the project--viper use Viper for configurationUse "cobra-cli [command] --help" for more information about a command.
4.1 init命令初始化应用程序
使用命令 cobra-cli init 来创建第一个应用程序,这个命令也是初始化一个应用程序的项目框架,具体的过程:
$ mkdir go-cobra
$ go mod init proj
$ cobra-cli init
Your Cobra application is ready at
......\go-cobra
自动生成了如下目录的程序:
程序代码如下:
main.go
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>*/
package mainimport "proj/cmd"func main() {cmd.Execute()
}
cmd/root.go
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>*/
package cmdimport ("os""github.com/spf13/cobra"
)// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{Use: "proj",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() {err := rootCmd.Execute()if err != nil {os.Exit(1)}
}func init() {// Here you will define your flags and configuration settings.// Cobra supports persistent flags, which, if defined here,// will be global for your application.// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.proj.yaml)")// Cobra also supports local flags, which will only run// when this action is called directly.rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
启动运行:
$ 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.
可以看出,用 cobra-cli init 命令初始化的项目, 生成了一个初始化的应用框架,但是没有任何逻辑功能,仅仅输
出一些描述性信息。
这个程序里,最重要的是 cmd/root.go 里的 rootCmd = &cobra.Command{} 这行程序,这里定义命令动作。
程序里的 init() 函数是对命令行的配置。
4.2 add 生成子命令subCmd
上面我们用 cobra-cli init 创建了应用程序框架,在程序 cmd/root.go 里有一个根命令 rootCmd,也就是说 init 命
令创建了一个根命令。执行 command 命令是 &cobra.Command{} 里的 Run 方法。
用 cobra-cli add 来为 rootCmd 创建一个子命令。
下面添加两个命令 ping 和 cat,这个子命令通常在一个单独的文件里。
$ cobra-cli add ping
ping created at ......\go-cobra
$ cobra-cli add cat
cat created at ......\go-cobra
自动生成了如下目录的程序:
查看文件:
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>*/
package cmdimport ("fmt""github.com/spf13/cobra"
)// pingCmd represents the ping command
var pingCmd = &cobra.Command{Use: "ping",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("ping called")},
}func init() {rootCmd.AddCommand(pingCmd)// Here you will define your flags and configuration settings.// Cobra supports Persistent Flags which will work for this command// and all subcommands, e.g.:// pingCmd.PersistentFlags().String("foo", "", "A help for foo")// Cobra supports local flags which will only run when this command// is called directly, e.g.:// pingCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>*/
package cmdimport ("fmt""github.com/spf13/cobra"
)// catCmd represents the cat command
var catCmd = &cobra.Command{Use: "cat",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("cat called")},
}func init() {rootCmd.AddCommand(catCmd)// Here you will define your flags and configuration settings.// Cobra supports Persistent Flags which will work for this command// and all subcommands, e.g.:// catCmd.PersistentFlags().String("foo", "", "A help for foo")// Cobra supports local flags which will only run when this command// is called directly, e.g.:// catCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
运行:
$ go run main.go --help
# 等价
$ 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.Usage:proj [command]Available Commands:cat A brief description of your commandcompletion Generate the autocompletion script for the specified shellhelp Help about any commandping A brief description of your commandFlags:-h, --help help for proj-t, --toggle Help message for toggleUse "proj [command] --help" for more information about a command.
ping 和 cat 已经被集成到 root.go 中。
$ go run main.go ping
ping called$ go run main.go cat
cat called
也可以自己编写文件然后进行手动添加。
子命令和根命令的关系一般通过程序 rootCmd.AddCommand()
方法确定。在程序里可以看到它在 init() 函数里。
Run 方法里可以添加自己需要实现的程序,一般这里的程序都是其他 package 里完成了具体逻辑,然后 Run 方法
里在调用这些程序。
4.3 给command添加flags
flag 命令行选项,也叫标识,对command命令行为的进一步指示操作。
用这个标识可以给 command 添加一些可选项。
根据 flag 选项作用范围不同,可以分为 2 类:
-
Persistent Flags,持久化的flag,全局范围
如果设置全局范围的flag,可以用这个来设置。它既可以给根命令设置选项值,也可以给子命令设置选项值。
-
Local Flags,局部flag
只对指定的command生效,比如某个子命令的 flag。
因为 flag 标识是在命令行后面不同位置使用,所以我们要在方法外定义一个变量,来分配存储使用这个标识符。
下面我们给 cat 命令添加命令行选项来实现全局 flag 和局部 flag。
4.3.1 全局flag
在 cmd/root.go
文件中添加一个变量 configFile
var configFile string
在 cmd/root.go
的 init() 函数中添加全局 flag,把 flag 值存储到变量 configFile 中。也就是读取命令行
–configfile 这个 flag 设置的值,然后赋值给程序里变量 configFile。
// 添加全局flag
rootCmd.PersistentFlags().StringVar(&configFile, "configfile", "$HOME/app.conf", "config file (default is $HOME/app.conf)")
在文件 cmd/cat.go
中的 catCmd(子命令) 里 Run 方法输出 configFile 值:
Run: func(cmd *cobra.Command, args []string) {fmt.Println("cat called")// 打印输出 namefmt.Println("print persistent flag configFile: ", configFile)
},
测试运行程序:
$ go run main.go cat -h
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.Usage:proj cat [flags]Flags:-h, --help help for catGlobal Flags:--configfile string config file (default is $HOME/app.conf) (default "$HOME/app.conf")
$ go run main.go cat --configfile /opt/app.yaml
cat called
print persistent flag configFile: /opt/app.yaml
当然也可以先编译 go build -o main.exe
,我用的win,然后在运行测试程序:
$ go build -o main.exe
$ main.exe cat --configfile /opt/app.yaml
cat called
print persistent flag configFile: /opt/app.yaml
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")
4.3.2 局部flag
一个 flag 赋值给本地变量,只能对指定的 command 生效。
我们在 cmd/ping.go
中测试局部 flag。
在 cmd/ping.go
文件中定义变量 ip,存储这个 flag 值。
// 定义局部flag
var ip string
在 cmd/ping.go
中的 init() 中添加下面代码,把值存储到 ip 上。
pingCmd.Flags().StringVarP(&ip, "ip", "i", "127.0.0.1", "ip")
在 pingCmd.Command{} 获取该值:
Run: func(cmd *cobra.Command, args []string) {fmt.Println("ping called")// 打印输出 namefmt.Println("print persistent flag configFile: ", configFile)// 打印输出local flag: dsnfmt.Println("(local flag) print ip: ", ip)}
测试运行:
$ go run main.go ping -h
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.Usage:proj ping [flags]Flags:-h, --help help for ping-i, --ip string ip (default "127.0.0.1")Global Flags:--configfile string config file (default is $HOME/app.conf) (default "$HOME/app.conf")
$ go run main.go ping --ip "192.168.164.195"
ping called
print persistent flag configFile: $HOME/app.conf
(local flag) print ip: 192.168.164.195
# 可以读取全局flag
$ go run main.go ping --ip "192.168.164.195" --configfile /opt/app.yaml
ping called
print persistent flag configFile: /opt/app.yaml
(local flag) print ip: 192.168.164.195
说明:local flag 局部选项,只能作用于指定的 command。
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")
4.3.3 设置flag必填项
比如给 cmd/ping.go
的 ip 这个 flag 设置必选项:
pingCmd.Flags().StringVarP(&ip, "ip", "i", "127.0.0.1", "ip")
// 把ip设置为必选项
pingCmd.MarkFlagRequired("ip")
flag 不设置 ip,运行程序:go run main.go ping
,报错:
$ go run main.go ping
Error: required flag(s) "ip" not set
加上 ip 运行,go run main.go ping --ip 192.168.164.195
,正常输出:
$ go run main.go ping --ip 192.168.164.195
ping called
print persistent flag configFile: $HOME/app.conf
(local flag) print ip: 192.168.164.195
flag 还可以做依赖,比如下面 username 和 password 必须同时接收到参数。
rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
rootCmd.MarkFlagsRequiredTogether("username", "password")
4.3.4 绑定配置
还可以绑定配置到 flags 上,用 viper。
我们可以在 init() 方法中添加绑定 flag 程序。
在 cmd/root.go
里面,添加如下内容:
var author string
func init() {rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")// 添加全局flagrootCmd.PersistentFlags().StringVar(&configFile, "configfile", "$HOME/app.conf", "config file (default is $HOME/app.conf)")rootCmd.PersistentFlags().StringVar(&author, "author", "zhangsan", "Author name for copyright attribution")viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}
在 cmd/ping.go
里面读取输出:
Run: func(cmd *cobra.Command, args []string) {fmt.Println("ping called")// 打印输出 namefmt.Println("print persistent flag configFile: ", configFile)// 打印输出local flag: dsnfmt.Println("(local flag) print ip: ", ip)fmt.Println("author is : ",author)fmt.Println("viper author: ",viper.Get("author"))
},
启动运行:
$ go run main.go ping --ip 192.168.164.195 --author zsx242030
ping called
print persistent flag configFile: $HOME/app.conf
(local flag) print ip: 192.168.164.195
author is : zsx242030
viper author: zsx242030
# 如果不加--author
go run main.go ping --ip 192.168.164.195
ping called
print persistent flag configFile: $HOME/app.conf
(local flag) print ip: 192.168.164.195
author is : zhangsan
viper author: zhangsan
这样就将 viper 配置和 flag 绑定,如果用户不设置 --author,将从配置中查找。
4.4 arguments 命令行参数设置
cobra 内置的参数验证也是比较多,NoArgs、OnlyValidArgs、MinimumNArgs、MaximumNArgs等等,可以满
足基本使用,如果有自己的特殊要求可以通过解析 arg 来实现。
可以用Command 的 Args 字段指定参数效验规则。
Cobra 也内置了一些规则:
- NoArgs:如果有任何命令行args参数,将会报错
- ArbitraryArgs:该命令接受任何参数
- OnlyValidArgs:如果该命令参数不在 Command 的 ValidArgs 中,将会报错
- MinimumArgs(int): 如果命令参数数目少于N个,将会报错
- MaximumArgs(int): 如果命令参数数目多于N个,将会报错
- ExactArgs(int): 如果命令参数数目不是N个,将会报错
- RangeArgs(min, max):如果命令参数数目范围不在(min, max),将会报错
内置效验规则的例子:
$ cobra-cli add test
test created at ......\go-cobra
// testCmd represents the test command
var testCmd = &cobra.Command{Use: "test",Short: "test short",Long: `test short`,Args: cobra.MinimumNArgs(5),Run: func(cmd *cobra.Command, args []string) {fmt.Println("test called")},
}
运行:
$ go run main.go test
Error: requires at least 5 arg(s), only received 0
$ go run main.go test aa bb cc dd ee
test called
自定义验证规则的例子:
// testCmd represents the test command
var testCmd = &cobra.Command{Use: "test",Short: "test short",Long: `test short`,Args: func(cmd *cobra.Command, args []string) error {if len(args) < 5 {return errors.New("requires at least 5 arg(s)")}if args[0] != "aa" {return errors.New("first must is aa")}return nil},Run: func(cmd *cobra.Command, args []string) {fmt.Println("test called")},
}
运行:
$ go run main.go test aa bb cc dd
Error: requires at least 5 arg(s)
$ go run main.go test ff bb cc dd ee
Error: first must is aa
$ go run main.go test aa bb cc dd ee
test called
4.5 钩子函数PreRun and PostRun Hooks
可以在执行命令之前或之后运行钩子函数。如果子命令未声明自己的 PersistentPreRun
和
PersistentPostRun
函数,则子命令将继承父命令的钩子函数。
函数的执行顺序为:
- PersistentPreRun
- PreRun
- Run
- PostRun
- PersistentPostRun
cmd/root.go
中添加如下内容:
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{Use: "proj",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) { },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)},
}
启动:
$ go run main.go cat arg1 arg2
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
cat called
print persistent flag configFile: $HOME/app.conf
Inside rootCmd PersistentPostRun with args: [arg1 arg2]
cmd/cat.go
中添加如下内容:
// catCmd represents the cat command
var catCmd = &cobra.Command{Use: "cat",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("cat called")// 打印输出 namefmt.Println("print persistent flag configFile: ", configFile)},PreRun: func(cmd *cobra.Command, args []string) {fmt.Printf("Inside subCmd PreRun with args: %v\n", args)},PostRun: func(cmd *cobra.Command, args []string) {fmt.Printf("Inside subCmd PostRun with args: %v\n", args)},
}
启动:
$ go run main.go cat arg1 arg2
Inside rootCmd PersistentPreRun with args: [arg1 arg2]
Inside subCmd PreRun with args: [arg1 arg2]
cat called
print persistent flag configFile: $HOME/app.conf
Inside subCmd PostRun with args: [arg1 arg2]
Inside rootCmd PersistentPostRun with args: [arg1 arg2]
4.6 错误处理函数和钩子函数
与上面的钩子函数功能一样,只不过这里可以返回错误,处理RunE 功能的执行先后顺序如下:
- PersistentPreRunE
- PreRunE
- RunE
- PostRunE
- PersistentPostRunE
RunE: func(cmd *cobra.Command, args []string) error {fmt.Printf("Inside subCmd Run with args: %v\n", args)return nil
},
4.7 为你的命令生成文档
Cobra 可以基于子命令、标志等生成文档。具体的使用方法和生产格式文档请点击下面链接:
https://pkg.go.dev/github.com/spf13/cobra/doc
- Man page docs
- Markdown docs
- Rest docs
- Yaml docs
这里我们生成 Markdown Docs
:
package mainimport ("github.com/spf13/cobra""github.com/spf13/cobra/doc""log"
)func main() {cmd := &cobra.Command{Use: "test",Short: "my test program",}err := doc.GenMarkdownTree(cmd, "./")if err != nil {log.Fatal(err)}
}
生成的内容:
## testmy test program### Options```-h, --help help for test
```###### Auto generated by spf13/cobra on 31-May-2023
你可以设置 cmd.DisableAutoGenTag = true
从而把文档中 Auto generated by spf13/cobra...
等字样删
掉。
4.8 help命令
命令: cobra-cli help
,可以清楚显示出对使用 cobra 有用的信息,比如命令提示:
$ cobra-cli help
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-cli [command]Available Commands:add Add a command to a Cobra Applicationcompletion Generate the autocompletion script for the specified shellhelp 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-cli-l, --license string name of license for the project--viper use Viper for configurationUse "cobra-cli [command] --help" for more information about a command.
你还可以定义自己的 help 命令或模板:
cmd.SetHelpCommand(cmd *Command)
cmd.setHelpCommand(f func(*Command, []string))
cmd.setHelpTemplate(s string)
正常情况下的输出:
go run main.go test -h
test shortUsage:proj test [flags]Flags:-h, --help help for testGlobal Flags:--author string Author name for copyright attribution (default "zhangsan")--configfile string config file (default is $HOME/app.conf) (default "$HOME/app.conf")
自己设置:
func init() {rootCmd.AddCommand(testCmd)testCmd.SetHelpTemplate(`Usage:test [flags]
Flags:-h, --help help for test`)
}
$ go run main.go test -h
Usage:test [flags]
Flags:-h, --help help for test