Cobra

Cobra 由以下两部分组成:

  • 用于创建 CLI 应用程序的库(cobra 库)

  • 用于生成 Cobra 应用程序的工具(cobra-cli)

概念

Cobra 建立在命令(command)、参数(arguments)和标志(flags)的结构之上,这三个组件共同定义了命令行应用程序的行为。

  • 命令(Commands)代表着命令行应用程序可以执行的特定操作

  • 参数(Args)是这些操作所针对的实体或项

  • 标志(Flags)是可以修改命令或参数行为的修饰符。

设计良好的命令行应用程序在使用时读起来像句子,其遵循的模式如下:

  • APPNAME VERB NOUN --ADJECTIVE,如:git clone URL --bare

  • APPNAME COMMAND ARG --FLAG,如:hugo server --port=1313

cobra-cli

cobra-cli 是一个命令行工具,用于生成 Cobra 应用程序。我们可以使用命令 go install github.com/spf13/cobra-cli@latest 来安装它。

初始化应用程序

安装完 cobra-cli 后,我们可以在项目根目录下执行命令 cobra-cli init 以初始化我们的 Cobra 应用程序。初始化完成后,我们的项目目录结构如下:

bash

复制代码

│ go.mod │ go.sum │ LICENSE # cobra-cli 生成的许可证文件 │ main.go # cobra-cli 生成的程序入口文件 │ └─cmd root.go # cobra-cli 生成的根命令文件

cobra-cli 生成的代码文件具有详细的注释,这里我们就不作讲解。

使用 cobro-cli init 时,我们还可以附加 flag :

bash

复制代码

cobra-cli init --author="Sue211213@163.com" --license=mit --viper #D:\workspace\golang\src\cobra-demo

  • --author 指定作者,作者信息会显示在文件的版权头信息和 LICENSE 文件中

  • --license 指定许可证,许可证信息会显示在 LICENSE 文件中

  • --viper 使用 viper

添加命令

初始化 Cobra 应用程序后,我们还可以使用 cobra-cli add 命令为我们的应用程序添加其他命令,如:

bash

复制代码

cobra-cli add serve cobra-cli add config cobra-cli add create -p 'configCmd'

  • -p 指定该命令的父级,默认情况下父级命令为 rootCmd

指定 command ,运行程序:

bash

复制代码

go run main.go serve #serve called go run main.go config #config called go run main.go config create #create called

配置文件

我们可以将 flag 写入到配置文件中,然后以读取配置文件的形式使用 flag。

创建配置文件 .cobra.yaml

yaml

复制代码

author: Sue211213 <sue211213@163.com> # 作者信息 license: MIT # 许可证信息,可以为 none,也可以自定义 useViper: true # 启用 viper

使用配置文件进行初始化:

bash

复制代码

cobra-cli init --config=.cobra.yaml #Using config file: .cobra.yaml #Your Cobra application is ready at #D:\workspace\golang\src\cobra-demo

cobra

使用标志

持久标志

我们可以为某个命令分配持久标志,持久标志能够被该命令及其所有子命令使用。

在下面这个示例中,我们为根命令分配了一个名为 verbose 的持久标志,由于是根命令上的持久标志,所以该标志是一个全局标志,可用于所有命令:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", } var cmdFoo = &cobra.Command{ Use: "foo", Run: func(cmd *cobra.Command, args []string) { fmt.Println("foo executed") if verbose { fmt.Println("verbose output enabled") } }, } var verbose bool func init() { rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose output") } func main() { rootCmd.AddCommand(cmdFoo) _ = rootCmd.Execute() }

bash

复制代码

go run main.go foo #foo executed go run main.go foo -v #foo executed #verbose output enabled

本地标志

我们可以为某个命令分配本地标志,本地标志只有该命令能够使用。

在下面这个示例中,我们为子命令 foo 分配了一个本地标志 name,该标志只能由子命令 foo 使用:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", } var cmdFoo = &cobra.Command{ Use: "foo", Run: func(cmd *cobra.Command, args []string) { name, _ := cmd.Flags().GetString("name") fmt.Printf("Hello %s\n", name) }, } func init() { cmdFoo.Flags().StringP("name", "n", "world", "The name to greet") } func main() { rootCmd.AddCommand(cmdFoo) _ = rootCmd.Execute() }

bash

复制代码

go run main.go -n=sue211213@163.com #Error: unknown shorthand flag: 'n' in -n=sue211213@163.com #... go run main.go foo -n=sue211213@163.com #Hello sue211213@163.com

将标志与配置绑定

我们可以使用 viper 将我们的标志和配置信息进行绑定。

在下面的这个示例中,我们将 name 标志与环境变量 APP_NAME 进行绑定:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Run: func(cmd *cobra.Command, args []string) { name := viper.GetString("name") fmt.Printf("Hello, %s\n", name) }, } func init() { rootCmd.Flags().StringP("name", "n", "world", "the name to greet") _ = viper.BindPFlag("name", rootCmd.Flags().Lookup("name")) viper.SetEnvPrefix("app") viper.AutomaticEnv() } func main() { _ = rootCmd.Execute() }

bash

复制代码

APP_NAME=Bob go run main.go #Hello, Bob

必填标志

我们可以为某个命令设置必填标志。

在下面这个示例中,我们为 app 命令设置了必填标志 name:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.Flags().StringP("name", "n", "World", "The name to greet") _ = rootCmd.MarkFlagRequired("name") } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go #Error: required flag(s) "name" not set #...

标志组

我们可以使用 MarkFlagsRequiredTogether 函数限制某些标志必须一起执行,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "myapp", Short: "A simple command line application", Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.Flags().StringP("username", "u", "", "Username (required)") rootCmd.Flags().StringP("password", "p", "", "Password (required)") rootCmd.MarkFlagsRequiredTogether("username", "password") } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --username=alice #Error: if any flags in the group [username password] are set they must all be set; missing [password] #...

我们可以使用 MarkFlagsRequiredTogether 函数限制某些标志不能同时出现,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "myapp", Short: "A simple command line application", Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.Flags().Bool("json", false, "Output in JSON format") rootCmd.Flags().Bool("yaml", false, "Output in YAML format") rootCmd.MarkFlagsMutuallyExclusive("json", "yaml") } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --json --yaml #Error: if any flags in the group [json yaml] are set none of the others can be; [json yaml] were all set #...

参数验证

我们可以对传入的参数进行验证。

内置验证

在下面这个示例中,我们为 hello 命令设置了有且只有一个参数验证;为 bye 命令设置了最少有一个参数验证;为 color 命令设置了选项参数验证:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", } var cmdHello = &cobra.Command{ Use: "hello [name]", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { }, } var cmdBye = &cobra.Command{ Use: "bye [name]...", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { }, } var cmdColor = &cobra.Command{ Use: "color [color]", Args: cobra.OnlyValidArgs, ValidArgs: []string{"red", "green", "blue"}, Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.AddCommand(cmdHello) rootCmd.AddCommand(cmdBye) rootCmd.AddCommand(cmdColor) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go hello Alice Bob #Error: accepts 1 arg(s), received 2 #... go run main.go bye #Error: requires at least 1 arg(s), only received 0 #... go run main.go color yellow #Error: invalid argument "yellow" for "app color" #...

自定义验证

在下面这个示例中,我们为主目录自定义了参数验证函数,要求传入参数必须为整数,且为2个:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Args: validateNumbers, Run: func(cmd *cobra.Command, args []string) { }, } func validateNumbers(cmd *cobra.Command, args []string) error { if len(args) != 2 { return fmt.Errorf("requires two arguments") } for _, arg := range args { if _, err := strconv.Atoi(arg); err != nil { return fmt.Errorf("invalid number: %s", arg) } } return nil } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go 1 2 3 #Error: requires two arguments go run main.go 1 a #Error: invalid number: a

帮助命令

当我们有子命令时,Cobra 会自动向我们的应用程序添加帮助命令和帮助标志。如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", } var cmdHello = &cobra.Command{ Use: "hello", } func init() { rootCmd.AddCommand(cmdHello) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go help #Usage: # app [command] # #Available Commands: # completion Generate the autocompletion script for the specified shell # help Help about any command # #Flags: # -h, --help help for app # #Additional help topics: # app hello # #Use "app [command] --help" for more information about a command. go run main.go --help #Usage: # app [command] # #Available Commands: # completion Generate the autocompletion script for the specified shell # help Help about any command # #Flags: # -h, --help help for app # #Additional help topics: # app hello # #Use "app [command] --help" for more information about a command.

自定义帮助信息

我们可以用 setHelpCommand 函数自定义帮助信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetHelpCommand", Run: func(cmd *cobra.Command, args []string) { }, } var cmdFoo = &cobra.Command{ Use: "foo", Short: "A subcommand that prints foo", Run: func(cmd *cobra.Command, args []string) { }, } var helpCmd = &cobra.Command{ Use: "help [command]", Short: "Help about any command", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { fmt.Println("This is an example app with SetHelpFunc.") fmt.Println("Usage:") fmt.Println(" app [command]") fmt.Println() fmt.Println("Available Commands:") fmt.Println(" foo A subcommand that prints foo") fmt.Println() } else { sub, _, err := cmd.Root().Find(args) if err != nil { fmt.Printf("Unknown help topic %q\n", args[0]) return } _ = sub.Help() } }, } func init() { rootCmd.AddCommand(cmdFoo) rootCmd.SetHelpCommand(helpCmd) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go help #This is an example app with SetHelpFunc. #Usage: # app [command] # #Available Commands: # foo A subcommand that prints foo go run main.go help foo #A subcommand that prints foo # #Usage: # app foo

我们还可以使用 SetHelpFunc 函数自定义帮助信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetHelpFunc", Run: func(cmd *cobra.Command, args []string) { }, } var cmdFoo = &cobra.Command{ Use: "foo", Short: "A subcommand that prints foo", Run: func(cmd *cobra.Command, args []string) { }, } func customHelp(cmd *cobra.Command, args []string) { if len(args) == 0 { fmt.Println("This is an example app with SetHelpFunc.") fmt.Println("Usage:") fmt.Println(" app [command]") fmt.Println() fmt.Println("Available Commands:") fmt.Println(" foo A subcommand that prints foo") fmt.Println() } else { sub, _, err := cmd.Root().Find(args) if err != nil { fmt.Printf("Unknown help topic %q\n", args[0]) return } _ = sub.Help() } } func init() { rootCmd.SetHelpFunc(customHelp) rootCmd.AddCommand(cmdFoo) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go help #This is an example app with SetHelpFunc. #Usage: # app [command] # #Available Commands: # foo A subcommand that prints foo go run main.go help foo #This is an example app with SetHelpFunc. #Usage: # app [command]

我们还可以使用 SetHelpTemplate 自定义帮助信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetHelpTemplate", Run: func(cmd *cobra.Command, args []string) { }, } var cmdFoo = &cobra.Command{ Use: "foo", Short: "A subcommand that prints foo", Run: func(cmd *cobra.Command, args []string) { }, } const customTemplate = {{with (or .Long .Short)}}{{. | trimTrailingWhitespaces}}{{end}} Usage: {{.UseLine}} {{if .HasAvailableSubCommands}} Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} {{.Name}} {{.Short}}{{end}}{{end}}{{end}} func init() { rootCmd.SetHelpTemplate(customTemplate) rootCmd.AddCommand(cmdFoo) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go help #An example app with SetHelpTemplate #Usage: # app [flags] # #Available Commands: # completion Generate the autocompletion script for the specified shell # foo A subcommand that prints foo # help Help about any command go run main.go help foo #A subcommand that prints foo #Usage: # app foo [flags]

用法信息

当我们提供无效标志时,Cobra 会向我们显示用法信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetUsageFunc", Run: func(cmd *cobra.Command, args []string) { }, } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --name #Error: unknown flag: --name #Usage: # app [flags] # #Flags: # -h, --help help for app

自定义用法信息

我们可以使用 SetUsageFunc 函数自定义用法信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetUsageFunc", Run: func(cmd *cobra.Command, args []string) { }, } func customUsage(cmd *cobra.Command) error { _, _ = fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n", cmd.Name()) _, _ = fmt.Fprintln(os.Stderr, "My app does something useful.") _, _ = fmt.Fprintln(os.Stderr, "") _, _ = fmt.Fprintln(os.Stderr, "Options:") cmd.Flags().VisitAll(func(flag *pflag.Flag) { _, _ = fmt.Fprintf(os.Stderr, " --%s=%s\n", flag.Name, flag.Usage) }) return nil } func init() { rootCmd.SetUsageFunc(customUsage) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --name #Error: unknown flag: --name #Usage: app [OPTIONS] #My app does something useful. # #Options: # --help=help for app

我们还可以使用 SetUsageTemplate 函数自定义用法信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SetUsageTemplate", Run: func(cmd *cobra.Command, args []string) { }, } const customTemplate = Usage: {{.UseLine}} My app does something useful. Options: {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}} func init() { rootCmd.SetUsageTemplate(customTemplate) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --name #Error: unknown flag: --name #Usage: app [flags] #My app does something useful. # #Options: # -h, --help help for app

版本标志

我们可以在根命令上设置版本字段,Cobra 会自动添加 version 标志,此标志的作用是输出应用的版本信息,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with Version and SetVersionTemplate", Version: "1.0.0", } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --version #app version 1.0.0

自定义版本信息

我们可以使用 cmd.SetVersionTemplate(s string) 函数自定义版本信息模板,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with Version and SetVersionTemplate", Version: "1.0.0", } const customTemplate = This is {{.Name}}, an example app with Version and SetVersionTemplate. The current version is {{.Version}}. func init() { rootCmd.SetVersionTemplate(customTemplate) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go --version #This is app, an example app with Version and SetVersionTemplate. #The current version is 1.0.0.

钩子函数

cobra 为我们提供了钩子函数,它们的执行顺序如下:

  • PersistentPreRun

  • PreRun

  • Run

  • PostRun

  • PersistentPostRun

注意,若子命令没有自己的钩子函数,将会继承父命令的钩子函数

go

复制代码

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) }, } func init() { rootCmd.AddCommand(subCmd) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.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: [] go run main.go sub #Inside rootCmd PersistentPreRun with args: [] #Inside subCmd PreRun with args: [] #Inside subCmd Run with args: [] #Inside subCmd PostRun with args: [] #Inside subCmd PersistentPostRun with args: []

出现 "unknown command" 时的建议

当发生 “unknown command” 错误时,Cobra 将打印建议,建议是根据注册的子命令自动生成的,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with DisableSuggestions", Run: func(cmd *cobra.Command, args []string) { }, } var cmdFoo = &cobra.Command{ Use: "foo", Short: "A subcommand that prints foo", Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.AddCommand(cmdFoo) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go f #Error: unknown command "f" for "app" # #Did you mean this? # foo # #Run 'app --help' for usage.

禁用建议

我们可以使用 command.DisableSuggestions = true 以禁用子命令建议,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with DisableSuggestions", DisableSuggestions: true, Run: func(cmd *cobra.Command, args []string) { }, } var cmdFoo = &cobra.Command{ Use: "foo", Short: "A subcommand that prints foo", Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.AddCommand(cmdFoo) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go f #Error: unknown command "f" for "app" #Run 'app --help' for usage.

我们还可以使用 SuggestFor 属性显式设置建议给定命令的名称,如:

go

复制代码

var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with SuggestFor", Run: func(cmd *cobra.Command, args []string) { }, } var cmdBar = &cobra.Command{ Use: "bar", Short: "A subcommand that prints bar", SuggestFor: []string{"qux"}, Run: func(cmd *cobra.Command, args []string) { }, } func init() { rootCmd.AddCommand(cmdBar) } func main() { _ = rootCmd.Execute() }

bash

复制代码

go run main.go qux #Error: unknown command "qux" for "app" # #Did you mean this? # bar # #Run 'app --help' for usage.

为命令生成文档

我们可以根据子命令、标志等生成以下格式的文档:

  • Markdown,如:

    go

    复制代码

    var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with documentation", Run: func(cmd *cobra.Command, args []string) { }, } func main() { err := doc.GenMarkdownTree(rootCmd, "./docs") if err != nil { log.Fatal(err) } }

  • ReStructured Text,如:

    go

    复制代码

    var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with documentation", } func main() { err := doc.GenReSTTree(rootCmd, "./docs") if err != nil { log.Fatal(err) } }

  • Man Page,如:

    go

    复制代码

    var rootCmd = &cobra.Command{ Use: "app", Short: "An example app with documentation", } func main() { header := &doc.GenManHeader{ Title: "APP", Section: "1", Date: &time.Time{}, Source: "Cobra Example App", Manual: "Cobra Example Manual", } err := doc.GenManTree(rootCmd, header, "./docs") if err != nil { log.Fatal(err) } }



链接:https://juejin.cn/post/7266452476378218548