Cobra是一个可以创建强大的现代化命令行应用的库,许多应用都使用了Cobra库构建应用,比如k8s,Hugo等;
Cobra同时也提供了一个脚手架,用来生成基于Cobra的应用框架。
Cobra是建立在commands,arguments和flags的结构上的,commands代表命令,args代表非选项参数,flags代表标志。
一个好的应用应该是易懂的,用户能够直观的与其交互,应用通常遵循这种模式:APPNAME VERB NOUN --ADJECTIVE
或者APPNAME COMMAND ARG --FLAG
比如以下示例:
hugo server --port=1313
git clone URL --bare
这里VERB表示动词,NOUN表示名词,ADJECTIVE表示形容词
Cobra的使用提供了两种方式:使用Cobra库或者cobra-cli脚手架
两种方式都需要先安装:
go install github.com/spf13/cobra/cobra@latest
go install github.com/spf13/cobra-cli@latest
▾ appName/
▾ cmd/
add.go
your.go
commands.go
here.go
main.go
其中main.go文件是很简洁的,它只做一件事,就是初始化cobra:
package main
import (
"{pathToYourApp}/cmd"
)
func main() {
cmd.Execute()
}
package cmd
import (
"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)
package main
import "cobrafast/cmd"
func main() {
cmd.Execute()
}
package cmd
import (
"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添加到根明了中
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ go run main.go -h
mycli is a cobra test code,this is long description
Usage:
mycli [flags]
mycli [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
version Print the version number of mycli
Flags:
-h, --help help for mycli
Use "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's
Usage:
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
RunE
方法,错误信息会被Execute
方法捕获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 version
version error
exit status 1
Cobra使用pflag解析命令行选项,Cobra中有两种类型的flag:一种是持久化的flag,一种是本地flag。
以下用示例来学习这个特性:
root.go新增以下代码:
var output string
func init() {
// 根命令定义一个持久化flag
rootCmd.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's
Usage:
mycli version [flags]
Flags:
-h, --help help for version
Global Flags:
-o, --output string output file name
通过-h查看帮助信息能看到,version命令多了一个Global Flags
接下来,将root.go的持久化flag改为本地flag:
func init() {
// 根命令定义一个本地flag
rootCmd.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's
Usage:
mycli version [flags]
Flags:
-h, --help help for version
TraverseChildren
,让cobra在执行目标命令先解析父命令的本地flag。package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var output string
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\n", output)
},
}
func init() {
getCmd.Flags().StringVarP(&output, "output", "o", "mycli", "output file name")
rootCmd.AddCommand(getCmd)
}
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)
}
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 version
unknown 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 version
unknown shorthand flag: 'o' in -o
常规情况下,-o是父命令get的本地flag,子命令version无法使用
TraverseChildren
设置为truevar 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 version
unknown shorthand flag: 'o' in -o
这时,使用命令如./cobrafast get -o hello version
,version命令就能先解析了get命令的本地flag了。
::: danger 注意
在这一块,我有几个点不是很能理解~
TraverseChildren
设置为true后,执行该命令时能解析父命令的flag才是,但实质上并不是,而是在root命令里添加;TraverseChildren
的地方,是在方法:ExecuteC
中,有以下代码:var flags []string
if 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,在写上子命令才可以成功执行。
:::
cobra使用的配置包是viper,我们可以将flag绑定到viper,这一部分与在viper中绑定flag是一样的了。
the_author: zhangsan
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)
}
var output string
var author string
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"))
},
}
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)
}
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get
name: mycli, author: zhangsan
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get --author lisi
name: mycli, author: lisi
lucas@Z-NB-0406:~/workspace/test/cobra_fast$ ./cobrafast get
name: mycli, author: lucas
默认情况下,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 mycli
Flags:
--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
MarkFlagsRequiredTogether()
MarkFlagsMutuallyExclusive()
对于这两种情况:
- 所有的本地flag和持久化flag都可以使用
- 一个flag可以出现在多个组中
- 一个组可以包含任意个flag
在命令中,除了flag标志外,通常也会有参数Arg,并且有时需要对这些参数进行验证,cobra提供了一些内置的验证函数:
NoArgs
: 如果存在任何的参数,将会报错ArbitaryArgs
:接受任意参数MinimumNArgs(int)
:接受至少N个参数,否则报错MaximumNArgs(int)
:接受至多N个参数,否则报错ExactArgs(int)
:只接收N个参数,如果个数不对,报错RangeArgs(min, max)
:参数个数在min和max之间,否则报错OnlyValidArgs
:如果没有一个参数是符合command的ValidArgs
字段指定的参数的话,就报错;这个要配合ValidArgs
使用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),
}
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 cobra
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
// Run the custom validation logic
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, World!")
},
}
cobra提供了一些钩子函数,在运行命令的Run函数时,PersistentPreRun
和PreRun
函数会在Run函数执行之前执行,PersistentPostRun
和PostRun
函数会在Run函数执行之后执行。
对于带有Persistent的函数,如果它的子命令没有实现这个函数,那么子命令将会继承父命令的Persistent*Run方法
这些钩子函数的执行顺序如下:
PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
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)
},
}
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]
cobra还支持其他比较有用的特性,比如:自定义Help命令,自定义帮助信息,添加–version标志,输入无效命令时的建议等,还可以生成命令的相关文档…