Cobra 是一个 Go 语言开发的命令行(CLI)框架,它提供了简洁、灵活且强大的方式来创建命令行程序。它包含:
一个用于创建命令行程序的库(Cobra 库)—— k8s中就是引入使用的这个库
一个用于快速生成基于 Cobra 库的命令行程序工具(Cobra 命令)
Cobra 是由 Go 团队成员 spf13 为 Hugo 项目创建的,并已被许多流行的 Go 项目所采用,如 Kubernetes、Helm、Docker (distribution)、Etcd 等。
官方网站:https://cobra.dev/
一、基础概念
Cobra 建立在命令、参数和标志这三个结构之上,要使用 Cobra 编写一个命令行程序,需要明确以下三个基本概念:
-
命令(COMMAND):命令表示要执行的操作。
-
参数(ARG):是命令的参数,一般用来表示操作的对象。
-
标志(FLAG):是命令的修饰,可以调整操作的行为。
命令比较容易理解,但是参数和标识这两个特别容易混淆!
一个好的命令行程序在使用时读起来像句子,用户会自然的理解并知道如何使用该程序。
要编写一个好的命令行程序,需要遵循的模式是
APPNAME VERB NOUN --ADJECTIVE或
APPNAME COMMAND ARG --FLAG。在这里 VERB 代表动词,NOUN 代表名词,ADJECTIVE 代表形容词。
1、命令用例一:
$ hugo server --port=1313 # 其中:server 是一个命令(子命令),port 是一个标志(1313 是标志的参数,但不是命令的参数 ARG)。
2、命令用例二:
$ git clone URL --bare # 其中:clone 是一个命令(子命令),URL 是命令的参数,bare 是标志
二、Cobra的快速开始 & 简单使用
Golang环境的安装就不说了,肯定是前提!
1、创建并初始化一个go项目:
[root@tcosmo-szls01 ~]# cd /jiguiquan/godata/ [root@tcosmo-szls01 godata]# mkdir mycobra [root@tcosmo-szls01 godata]# cd mycobra/ [root@tcosmo-szls01 mycobra]# go mod init mycobra go: creating new go.mod: module mycobra
2、安装 cobra 包:
# 根据官网手册直接安装的命令是错的,不知道何时会修改: [root@tcosmo-szls01 mycobra]# go get -u github.com/spf13/cobra/cobra@v1.0.0
安装好后,就可以像其他 Go 语言库一样导入 Cobra 包并使用了!
import "github.com/spf13/cobra"
3、创建一个命令mycobra:
编写一个.go文件 mycobra/cmd/root.go ,其中自定义了一个命令:mycobra
package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "mycobra", Short: "mycobra是吉桂权的第一个cobra应用", Long: `mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码!`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("run mycobra...") }, } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
cobra.Command 是一个结构体,代表一个命令,其各个属性含义如下:
Use:命令的名称;
Short:代表当前命令的简短描述;
Long:表示当前命令的完整描述;
Run:属性是一个函数,当执行命令时会调用此函数;
rootCmd.Execute() 是命令的执行入口,其内部会解析 os.Args[1:] 参数列表(默认情况下是这样,也可以通过 Command.SetArgs 方法设置参数),然后遍历命令树,为命令找到合适的匹配项和对应的标志。
4、创建程序入口 main.go:
package main import ( "mycobra/cmd" ) func main() { cmd.Execute() }
main.go 代码实现非常简单,只在 main 函数中调用了 cmd.Execute() 函数,来执行命令
5、编译并运行 mycobra 命令:
# 编译: [root@tcosmo-szls01 mycobra]# go build -o mycobra ## 运行: [root@tcosmo-szls01 mycobra]# ./mycobra run mycobra...
以上我们编译并执行了 mycobra 程序,输出内容正是 cobra.Command 结构体中 Run 函数内部代码的执行结果!
6、再使用 –help 查看这个程序的使用帮助:
# 查看帮助(--help或者): [root@tcosmo-szls01 mycobra]# ./mycobra --help [root@tcosmo-szls01 mycobra]# ./mycobra help mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码! Usage: mycobra [flags] Flags: -h, --help help for mycobra # hugo 命令用法为 mycobra [flags],如 mycobra --help。 # 这个命令行程序自动支持了 -h/--help 标志。
这里打印了 cobra.Command 结构体中 Long 属性的内容,如果 Long 属性不存在,则打印 Short 属性内容!
7、查看目前整个项目的代码结构:
[root@tcosmo-szls01 godata]# tree mycobra/ mycobra/ ├── cmd │ └── root.go ├── go.mod ├── go.sum ├── main.go └── mycobra
总结:
以上就是使用 Cobra 编写一个命令行程序最常见的套路,这也是 Cobra 推荐写法。
Cobra 程序目录结构基本如此,main.go 作为命令行程序的入口,不要写过多的业务逻辑,所有命令都应该放在 cmd/ 目录下,以后不管编写多么复杂的命令行程序都可以这么来设计。
8、为了让我们的命令全局生效,我们在环境变量中添加一下我们命令的位置:
[root@tcosmo-szls01 mycobra]# vim /etc/profile.d/myenv.sh # 内容如下: export GOROOT="/usr/local/go19" export GOPATH=/gopath export PATH=$PATH:$GOROOT/bin:/jiguiquan/godata/mycobra # 使生效: [root@tcosmo-szls01 mycobra]# source /etc/profile # 检验(任意位置): [root@tcosmo-szls01 mycobra]# mycobra run mycobra...
三、中级用法(添加子命令、自动补齐)
1、添加第一个子命令:
再创建一个 cmd/version.go 文件,内容如下:
package cmd import ( "fmt" "github.com/spf13/cobra" ) var versionCmd = &cobra.Command{ Use: "version", Short: "版本号", Long: `所有的软件都应该有版本信息,这是吉桂权的mycobra`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("v0.0.1") }, } func init() { rootCmd.AddCommand(versionCmd) }
与定义 rootCmd 一样,我们可以使用 cobra.Command 定义其他命令,并通过 rootCmd.AddCommand() 方法将其添加为 rootCmd 的一个子命令。
重新编译运行:
[root@tcosmo-szls01 mycobra]# go build -o mycobra [root@tcosmo-szls01 mycobra]# ./mycobra version v0.0.1
我们再来看看 mycobra 的 help 信息(很明显多了个version命令):
[root@tcosmo-szls01 mycobra]# ./mycobra --help mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码! Usage: mycobra [flags] mycobra [command] Available Commands: help Help about any command version 版本号 Flags: -h, --help help for mycobra Use "mycobra [command] --help" for more information about a command.
我们进一步看看 version 这个子命令的 help 信息:
[root@tcosmo-szls01 mycobra]# ./mycobra version --help 所有的软件都应该有版本信息,这是吉桂权的mycobra Usage: mycobra version [flags] Flags: -h, --help help for version
2、自动补全:
其实所谓的自动补全,是创建一个自动补全的命令,然后根据这个命令的提示,完成真正的自动补全的安装!
创建一个 cmd/completion.go 文件,内容如下(自己可以在官网或者github上找):
package cmd import ( "os" "github.com/abiosoft/colima/cmd/root" "github.com/spf13/cobra" ) // completionCmd represents the completion command func completionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "completion [bash|zsh|fish|powershell]", Short: "Generate completion script", Long: `To load completions: Bash: $ source <(colima completion bash) # To load completions for each session, execute once: # Linux: $ colima completion bash > /etc/bash_completion.d/colima # macOS: $ colima completion bash > /usr/local/etc/bash_completion.d/colima Zsh: # If shell completion is not already enabled in your environment, # you will need to enable it. You can execute the following once: $ echo "autoload -U compinit; compinit" >> ~/.zshrc # To load completions for each session, execute once: $ colima completion zsh > "${fpath[1]}/_colima" # You will need to start a new shell for this setup to take effect. fish: $ colima completion fish | source # To load completions for each session, execute once: $ colima completion fish > ~/.config/fish/completions/colima.fish PowerShell: PS> colima completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: PS> colima completion powershell > colima.ps1 # and source this file from your PowerShell profile. `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Run: func(cmd *cobra.Command, args []string) { switch args[0] { case "bash": _ = cmd.Root().GenBashCompletion(os.Stdout) case "zsh": _ = cmd.Root().GenZshCompletion(os.Stdout) case "fish": _ = cmd.Root().GenFishCompletion(os.Stdout, true) case "powershell": _ = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) } }, } return cmd } func init() { root.Cmd().AddCommand(completionCmd()) }
当然,中间的英文描述,我们也可以自己汉化一下!
重新编译一下:
[root@tcosmo-szls01 mycobra]# go build -o mycobra [root@tcosmo-szls01 mycobra]# ./mycobra -h mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码! Usage: mycobra [flags] mycobra [command] Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command version 版本号 Flags: -h, --help help for mycobra Use "mycobra [command] --help" for more information about a command.
当然,此时还是没有实现自动补全的,只是多了个completion命令,不信你输入个 ./mycobra v 然后 tab 键看看是否实现了自动补全!^_^
3、如何使用自动补全功能?
好坏先执行一下 completion 子命令看看这个命令到底是干嘛的:
[root@tcosmo-szls01 mycobra]# ./mycobra completion Generate the autocompletion script for mycobra for the specified shell. See each sub-command's help for details on how to use the generated script. Usage: mycobra completion [command] Available Commands: bash Generate the autocompletion script for bash fish Generate the autocompletion script for fish powershell Generate the autocompletion script for powershell zsh Generate the autocompletion script for zsh Flags: -h, --help help for completion Use "mycobra completion [command] --help" for more information about a command.
哦,这个命令告诉了我们,可以支持 bash/fish/powershell/zsh 等类型的自动补全!
那我们肯定得弄清楚我们是哪一类?
[root@tcosmo-szls01 mycobra]# echo $0 -bash
原来我们是bash!
那我们就进一步地查看帮助文档:
[root@tcosmo-szls01 mycobra]# ./mycobra completion bash -h Generate the autocompletion script for the bash shell. This script depends on the 'bash-completion' package. If it is not installed already, you can install it via your OS's package manager. To load completions in your current shell session: source <(mycobra completion bash) To load completions for every new session, execute once: #### Linux: mycobra completion bash > /etc/bash_completion.d/mycobra #### macOS: mycobra completion bash > $(brew --prefix)/etc/bash_completion.d/mycobra You will need to start a new shell for this setup to take effect. Usage: mycobra completion bash Flags: -h, --help help for bash --no-descriptions disable completion descriptions
哦豁,清除啦,我们还需要执行以下的一系列命令:
# 上面的提示说了,脚本是依赖于bash-completion的 yum install -y bash-completion ## 当前会话生效 source <(mycobra completion bash) ### 此时当前会话就已经生效了,但是我们想每一次都能支持,那么就还需要执行下面的命令: mycobra completion bash > /etc/bash_completion.d/mycobra #### 下面命令是我加的,刷新了下bash_completion source /usr/share/bash-completion/bash_completion
此时,我们再来验证自动补齐是否生效:验证已生效!
四、关于命令行标识Flag
持久标识:该标志将可用于它所分配的命令以及该命令下的所有子命令;(对于全局标志,可以定义在根命令 rootCmd 上)
本地标识:该标志只适用于当前命令本身;
必选标识:必须提供,如果不提供,则直接报错;(默认情况下,标志都是可选的)
父命令的本地标识:默认情况下,Cobra 仅解析目标命令上的本地标志,忽略父命令上的本地标志;
那如果我们想使用父命令的本地标识怎么办?—— 可以通过父命令的 TraverseChildren 属性为 true 实现传递。
关于上面这几点,我将在下面的代码中一一解释!
1、修改 cmd/root.go ,在根命令中增加持久标识、本地标识、必选标识:
package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "mycobra", TraverseChildren: true, Short: "mycobra是吉桂权的第一个cobra应用", Long: `mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码!`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("run mycobra...") fmt.Printf("Verbose: %v\n", Verbose) fmt.Printf("Source: %v\n", Source) fmt.Printf("Region: %v\n", Region) }, } // 持久标识 var Verbose bool // 本地标识 var Source string // 必选标识 var Region string func init() { rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") rootCmd.MarkFlagRequired("region") } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
在根命令中,我们定义了:
Verbose:持久标识
Source:本地标识
Region:必选标识
TraverseChildren:true(允许向子命令传递)
2、我们再定义一个 cmd/print.go 子命令:
package cmd import ( "fmt" "github.com/spf13/cobra" ) var printCmd = &cobra.Command{ Use: "print [OPTIONS] [COMMANDS]", Run: func(cmd *cobra.Command, args []string) { fmt.Println("run print...") fmt.Printf("本地的printFlag: %v\n", printFlag) fmt.Printf("父命令中的Verbose: %v\n", Verbose) fmt.Printf("父命令中的Source: %v\n", Source) }, } // 本地标志 var printFlag string func init() { rootCmd.AddCommand(printCmd) printCmd.Flags().StringVarP(&printFlag, "flag", "f", "", "print flag for local") }
在print子命令中,我们定义了:
printFlag:print子命令的本地标识
期望打印出Source这个“父命令的本地标识”
3、执行编译,先看看新命令和标识都是否生效了:
[root@tcosmo-szls01 mycobra]# go build -o mycobra [root@tcosmo-szls01 mycobra]# mycobra -h mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码! Usage: mycobra [flags] mycobra [command] Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command print version 版本号 Flags: -h, --help help for mycobra -r, --region string AWS region (required) -s, --source string Source directory to read from -v, --verbose verbose output Use "mycobra [command] --help" for more information about a command.
可以看到,现在有4个命令,和3个新标识。(–help不算,是cobra自带的)
4、验证:必选标识 region 不提供的话会报错:
# 提供region标识,运行正常 [root@tcosmo-szls01 mycobra]# mycobra -v -s test-source -r test-region run mycobra... Verbose: true Source: test-source Region: test-region # 不提供region标识,运行报错 [root@tcosmo-szls01 mycobra]# mycobra -v -s test-source Error: required flag(s) "region" not set
5、在 TraverseChildren=true 时,父命令中的 持久标识/本地标识 都可以正常传递给子命令:
# 先看看print子命令的手册 [root@tcosmo-szls01 mycobra]# mycobra print -h Usage: mycobra print [OPTIONS] [COMMANDS] [flags] Flags: -f, --flag string print flag for local -h, --help help for print Global Flags: -v, --verbose verbose output ## 父命令中的 Verbose 和 Source 都可以传递给 print 子命令 [root@tcosmo-szls01 mycobra]# mycobra -v -s test-source print -f test-sub-flag 本地的printFlag: test-sub-flag 父命令中的Verbose: true 父命令中的Source: test-source
6、在 TraverseChildren=false 时,父命令中的 持久标识可以正常传递给子命令,但是本地标识不可以:
我们需要将根命令中的 TraverseChildren 属性值改为false,然后重新编译:
# 首先持久标识Verbose是可以正常传递的 [root@tcosmo-szls01 mycobra]# mycobra -v print -f test-sub-flag 本地的printFlag: test-sub-flag 父命令中的Verbose: true 父命令中的Source: ## 但是父命令本地标识Resource是没办法传递给子命令的 [root@tcosmo-szls01 mycobra]# mycobra -v -s test-source print -f test-sub-flag Error: unknown shorthand flag: 's' in -s
五、参数验证与钩子函数Hooks
1、参数验证:
在执行命令行程序时,我们可能需要对命令参数进行合法性验证,cobra.Command 中的 Args 属性提供了此功能;
Args 属性类型为一个函数:func(cmd *Command, args []string) error,可以用来验证参数;
Cobra 内置了以下验证函数:
-
NoArgs:如果存在任何命令参数,该命令将报错。
-
ArbitraryArgs:该命令将接受任意参数。
-
OnlyValidArgs:如果有任何命令参数不在 Command 的 ValidArgs 字段中,该命令将报错。
-
MinimumNArgs(int):如果没有至少 N 个命令参数,该命令将报错。
-
MaximumNArgs(int):如果有超过 N 个命令参数,该命令将报错。
-
ExactArgs(int):如果命令参数个数不为 N,该命令将报错。
-
ExactValidArgs(int):如果命令参数个数不为 N,或者有任何命令参数不在 Command 的 ValidArgs 字段中,该命令将报错。
-
RangeArgs(min, max):如果命令参数的数量不在预期的最小数量 min 和最大数量 max 之间,该命令将报错。
内置验证参数的用法如下:
// 修改 cmd/version.go 文件为例 package cmd import ( "fmt" "github.com/spf13/cobra" ) var versionCmd = &cobra.Command{ Use: "version", Short: "版本号", Long: `所有的软件都应该有版本信息,这是吉桂权的mycobra`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("v0.0.1") }, Args: cobra.MaximumNArgs(2), // 使用内置的验证函数,位置参数多于 2 个则报错 } func init() { rootCmd.AddCommand(versionCmd) }
重新编译后测试:
# 传入2个参数,没有问题 [root@tcosmo-szls01 mycobra]# mycobra version a b v0.0.1 ## 传入大于2个参数,则报以下错误 [root@tcosmo-szls01 mycobra]# mycobra version a b c Error: accepts at most 2 arg(s), received 3
当然,我们还可以自定义验证参数:
// 简单修改 cmd/print.go 为例 package cmd import ( "errors" "fmt" "github.com/spf13/cobra" ) var printCmd = &cobra.Command{ Use: "print [OPTIONS] [COMMANDS]", Run: func(cmd *cobra.Command, args []string) { fmt.Println("run print...") // 命令行位置参数列表:例如执行 `hugo print a b c d` 将得到 [a b c d] fmt.Printf("args: %v\n", args) }, // 使用自定义验证函数 Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires at least one arg") } if len(args) > 4 { return errors.New("the number of args cannot exceed 4") } if args[0] != "a" { return errors.New("first argument must be 'a'") } return nil }, } // 本地标志 var printFlag string func init() { rootCmd.AddCommand(printCmd) printCmd.Flags().StringVarP(&printFlag, "flag", "f", "", "print flag for local") }
重新编译后测试:
# 正常输入参数在1~4之间,且以a开头,没有问题 [root@tcosmo-szls01 mycobra]# mycobra print a b c d run print... args: [a b c d] ## 如果参数不满足以上条件,则报错 [root@tcosmo-szls01 mycobra]# mycobra print b c d a Error: first argument must be 'a'
2、钩子函数Hooks:
在执行 Run 函数前后,我么可以执行一些钩子函数,其作用和执行顺序如下:
-
PersistentPreRun:在 PreRun 函数执行之前执行,对此命令的子命令同样生效。
-
PreRun:在 Run 函数执行之前执行。
-
Run:执行命令时调用的函数,用来编写命令的业务逻辑。
-
PostRun:在 Run 函数执行之后执行。
-
PersistentPostRun:在 PostRun 函数执行之后执行,对此命令的子命令同样生效。
我们直接修改rootCmd来演示:
package cmd import ( "fmt" "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "mycobra", TraverseChildren: false, Short: "mycobra是吉桂权的第一个cobra应用", Long: `mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码!`, PersistentPreRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PersistentPreRun") }, PreRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PreRun") }, Run: func(cmd *cobra.Command, args []string) { fmt.Println("run mycobra...") // fmt.Printf("Verbose: %v\n", Verbose) // fmt.Printf("Source: %v\n", Source) // fmt.Printf("Region: %v\n", Region) }, PostRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PostRun") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PersistentPostRun") }, } // 持久标识 var Verbose bool // 本地标识 var Source string // 必选标识 var Region string func init() { rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") //rootCmd.MarkFlagRequired("region") } func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
重新编译后测试:
# 首先验证主命令本身 [root@tcosmo-szls01 mycobra]# mycobra mycobra PersistentPreRun mycobra PreRun run mycobra... mycobra PostRun mycobra PersistentPostRun ## 其中 PersistentPreRun、PersistentPostRun 两个函数对子命令同样生效。 [root@tcosmo-szls01 mycobra]# mycobra version mycobra PersistentPreRun v0.0.1 mycobra PersistentPostRun
3、Hooks函数对应的<Hooks>E版本:
以上所有的Hooks函数都有对应的<Hooks>E,其中E代表Error,即可以支持抛出Error:
-
PersistentPreRunE
-
PreRunE
-
RunE
-
PostRunE
-
PersistentPostRunE
在rootCmd中增加 PersistentPreRunE 和 PreRunE 函数:
var rootCmd = &cobra.Command{ Use: "mycobra", TraverseChildren: false, Short: "mycobra是吉桂权的第一个cobra应用", Long: `mycobra是吉桂权的第一个cobra应用, 熟悉cobra框架,是为了更好地阅读kubernetes源码!`, PersistentPreRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PersistentPreRun") }, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { fmt.Println("mycobra PersistentPreRunE") return nil }, PreRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PreRun") }, PreRunE: func(cmd *cobra.Command, args []string) error { fmt.Println("mycobra PreRunE") return errors.New("PreRunE err") }, Run: func(cmd *cobra.Command, args []string) { fmt.Println("run mycobra...") //fmt.Printf("Verbose: %v\n", Verbose) //fmt.Printf("Source: %v\n", Source) //fmt.Printf("Region: %v\n", Region) }, PostRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PostRun") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { fmt.Println("mycobra PersistentPostRun") }, }
重新编译后测试:
[root@tcosmo-szls01 mycobra]# mycobra mycobra PersistentPreRunE mycobra PreRunE Error: PreRunE err Usage: mycobra [flags] mycobra [command] Available Commands: completion Generate the autocompletion script for the specified shell help Help about any command print version 版本号 Flags: -h, --help help for mycobra -r, --region string AWS region (required) -s, --source string Source directory to read from -v, --verbose verbose output Use "mycobra [command] --help" for more information about a command. PreRunE err
根据以上的测试结果,我们可以得出以下结论:
同时定义了 Hooks 和 它对应的 <Hooks>E 函数时,只会走 <Hooks>E 函数逻辑;
当 <Hooks>E 函数不抛出 Error 而是 nil 时,和原 Hooks 函数效果一样;
当 <Hooks>E 函数抛出 Error 后,程序会终止运行并打印错误信息;
补充说明:如果子命令 中定义了自己的 Hooks 或者 <Hooks>E,则可以覆盖父命令传递下来的 PersistentHooks。
六、定义自己的help命令 + Usage Message
1、定义自己的help命令:
默认情况下,我们可以使用 hugo help command 语法查看子命令的帮助信息,也可以使用 hugo command -h/–help 查看!
# 三者效果基本相同 ## 唯一的区别是,使用 help 命令查看帮助信息时会执行PersistentHooks钩子函数(因为help也是mycobra的子命令) mycobra help version mycobra version -h mycobra version --help
如果我们对 Cobra 自动生成的帮助命令不满意,我们可以通过以下方法自定义帮助命令或模板:
// 后两者也适用于任何子命令 cmd.SetHelpCommand(cmd *Command) cmd.SetHelpFunc(f func(*Command, []string)) cmd.SetHelpTemplate(s string)
我们可以使用 rootCmd.SetHelpCommand 来控制 help 命令输出,使用 rootCmd.SetHelpFunc 来控制 -h/–help 输出。
func Execute() { // 自定义了一个Command,赋值给HelpCommand,作为 help 命令的输出 rootCmd.SetHelpCommand(&cobra.Command{ Use: "help", Short: "Custom help command", Hidden: true, Run: func(cmd *cobra.Command, args []string) { fmt.Println("自定义的help命令输出") }, }) // 作为 -h/--help 的输出 rootCmd.SetHelpFunc(func(command *cobra.Command, strings []string) { fmt.Println(strings) // 把参数都打印出来看看是啥 }) if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
重新编译后测试:
# help命令是会打印出父命令(root命令)的PersistentHooks的 [root@tcosmo-szls01 mycobra]# mycobra help version mycobra PersistentPreRunE 自定义的help命令输出 mycobra PersistentPostRun ## -h/--help 标识,传入的strings其实是 命令 + 标识 + 参数列表(除主命令后的所有) [root@tcosmo-szls01 mycobra]# mycobra version -h a b c d [version -h a b c d]
2、使用 rootCmd.SetHelpTemplate 设置帮助信息模板:
// {{.UseLine}}: 获取当前命令的Use值 // {{.Short}}: 获取当前命令的Short值 // Commands: 遍历当前命令的子命令 func Execute() { rootCmd.SetHelpTemplate(`Custom Help Template: Usage: {{.UseLine}} Description: {{.Short}} Commands: {{- range .Commands}} {{.Name}}: {{.Short}} {{- end}} `) if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
重新编译后测试:
# 使用 -h/--help 标识 [root@tcosmo-szls01 mycobra]# mycobra -h Custom Help Template: Usage: mycobra [flags] Description: mycobra是吉桂权的第一个cobra应用 Commands: completion: Generate the autocompletion script for the specified shell help: Help about any command print: version: 版本号 ## 使用help子命令 [root@tcosmo-szls01 mycobra]# mycobra help mycobra PersistentPreRunE Custom Help Template: Usage: mycobra [flags] Description: mycobra是吉桂权的第一个cobra应用 Commands: completion: Generate the autocompletion script for the specified shell help: Help about any command print: version: 版本号 mycobra PersistentPostRun ### 使用help子命令打印version的帮助信息 [root@tcosmo-szls01 mycobra]# mycobra help version mycobra PersistentPreRunE Custom Help Template: Usage: mycobra version [flags] Description: 版本号 Commands: mycobra PersistentPostRun
根据以上测试,我们得出以下结论:
无论使用 help 命令查看帮助信息,还是使用 -h 查看帮助信息,其输出内容都遵循我们自定义的模版格式;
3、自定义自己的Usage Massage:
当用户提供无效标志或无效命令时,Cobra 通过向用户显示 Usage 来提示用户如何正确的使用命令;
这个输出格式默认与 help 信息一样,我们也可以进行自定义。Cobra 提供了如下两个方法,来控制输出:
cmd.SetUsageFunc(f func(*Command) error) cmd.SetUsageTemplate(s string)
直接在上面的代码中增加一个SetUsageFunc函数:
func Execute() { rootCmd.SetHelpTemplate(`Custom Help Template: Usage: {{.UseLine}} Description: {{.Short}} Commands: {{- range .Commands}} {{.Name}}: {{.Short}} {{- end}} `) rootCmd.SetUsageFunc(func(command *cobra.Command) error { return errors.New("自定义 Usage Message") }) if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } }
重新编译后测试:执行一个不存在的标识:
[root@tcosmo-szls01 mycobra]# mycobra --demo Error: unknown flag: --demo Error: 自定义 Usage Message
另外的 SetUsageTemplate() 就不演示了,和上面的help类似!
七、未知命令提示
在我们使用 git 等完善的命令时,有一个非常好用的功能,能够对用户输错的未知命令智能提示:
[root@tcosmo-szls01 mycobra]# git stat git: 'stat' is not a git command. See 'git --help'. Did you mean one of these? status stage stash这个功能非常实用,幸运的是,Cobra 自带了此功能!
不需要我们做任何事情,这个智能提示功能是自动开启的:
[root@tcosmo-szls01 mycobra]# mycobra vers Error: unknown command "vers" for "mycobra" Did you mean this? version [root@tcosmo-szls01 mycobra]# mycobra pri Error: unknown command "pri" for "mycobra" Did you mean this? print
但是,如果你想彻底关闭此功能,可以使用如下设置:
command.DisableSuggestions = true
我觉得咱们应该不会主动去关闭这个优秀的功能吧!
八、生成文档
Cobra 支持生成 Markdown、ReStructured Text、Man Page 三种格式文档;
这里以生成 Markdown 格式文档为例,来演示下 Cobra 这一强大功能;
我们可以通过定义一个标志 md-docs 来决定是否生成文档。
// 普通本地标识,决定是否需要生成markdown文档 var MarkdownDocs bool func init() { rootCmd.Flags().BoolVarP(&MarkdownDocs, "md-docs", "m", false, "gen Markdown docs") ... } // 定义生成markdown文档的方法 func GenDocs() { if MarkdownDocs { if err := doc.GenMarkdownTree(rootCmd, "./docs/md"); err != nil { fmt.Println(err) os.Exit(1) } } }
这些都完成后,在 main.go 入口函数中调用 GenDocs() 方法即可:
import ( "fmt" "os" "github.com/spf13/cobra/doc" "errors" "github.com/spf13/cobra" ) package main import ( "mycobra/cmd" ) func main() { cmd.Execute() cmd.GenDocs() }
重新编译后测试:
# 编译时,可能会遇到缺包问题,我是遇到以下两个(记录一下): go get github.com/spf13/cobra/doc@v1.6.1 go get github.com/abiosoft/colima/cmd/root ## 以下两个命令选一个执行 [root@tcosmo-szls01 mycobra]# mycobra -m [root@tcosmo-szls01 mycobra]# mycobra --md-docs run mycobra...
将生成主命令及所有子命令的MarkDown文档:
[root@tcosmo-szls01 mycobra]# tree docs/md docs/md ├── mycobra_completion_bash.md ├── mycobra_completion_fish.md ├── mycobra_completion.md ├── mycobra_completion_powershell.md ├── mycobra_completion_zsh.md ├── mycobra.md ├── mycobra_print.md └── mycobra_version.md