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 应用程序。初始化完成后,我们的项目目录结构如下:
│ go.mod
│ go.sum
│ LICENSE # cobra-cli 生成的许可证文件
│ main.go # cobra-cli 生成的程序入口文件
│
└─cmd
root.go # cobra-cli 生成的根命令文件
cobra-cli 生成的代码文件具有详细的注释,这里我们就不作讲解。
使用 cobro-cli init
时,我们还可以附加 flag :
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
命令为我们的应用程序添加其他命令,如:
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd'
-p
指定该命令的父级,默认情况下父级命令为rootCmd
指定 command
,运行程序:
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
:
author: Sue211213 <sue211213@163.com> # 作者信息
license: MIT # 许可证信息,可以为 none,也可以自定义
useViper: true # 启用 viper
使用配置文件进行初始化:
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 的持久标志,由于是根命令上的持久标志,所以该标志是一个全局标志,可用于所有命令:
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()
}
go run main.go foo
#foo executed
go run main.go foo -v
#foo executed
#verbose output enabled
本地标志
我们可以为某个命令分配本地标志,本地标志只有该命令能够使用。
在下面这个示例中,我们为子命令 foo 分配了一个本地标志 name,该标志只能由子命令 foo 使用:
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()
}
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 进行绑定:
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()
}
APP_NAME=Bob go run main.go
#Hello, Bob
必填标志
我们可以为某个命令设置必填标志。
在下面这个示例中,我们为 app 命令设置了必填标志 name:
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()
}
go run main.go
#Error: required flag(s) "name" not set
#...
标志组
我们可以使用 MarkFlagsRequiredTogether
函数限制某些标志必须一起执行,如:
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()
}
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
函数限制某些标志不能同时出现,如:
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()
}
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 命令设置了选项参数验证:
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()
}
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个:
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()
}
go run main.go 1 2 3
#Error: requires two arguments
go run main.go 1 a
#Error: invalid number: a
帮助命令
当我们有子命令时,Cobra 会自动向我们的应用程序添加帮助命令和帮助标志。如:
var rootCmd = &cobra.Command{
Use: "app",
}
var cmdHello = &cobra.Command{
Use: "hello",
}
func init() {
rootCmd.AddCommand(cmdHello)
}
func main() {
_ = rootCmd.Execute()
}
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
函数自定义帮助信息,如:
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()
}
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
函数自定义帮助信息,如:
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()
}
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
自定义帮助信息,如:
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()
}
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 会向我们显示用法信息,如:
var rootCmd = &cobra.Command{
Use: "app",
Short: "An example app with SetUsageFunc",
Run: func(cmd *cobra.Command, args []string) {
},
}
func main() {
_ = rootCmd.Execute()
}
go run main.go --name
#Error: unknown flag: --name
#Usage:
# app [flags]
#
#Flags:
# -h, --help help for app
自定义用法信息
我们可以使用 SetUsageFunc
函数自定义用法信息,如:
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()
}
go run main.go --name
#Error: unknown flag: --name
#Usage: app [OPTIONS]
#My app does something useful.
#
#Options:
# --help=help for app
我们还可以使用 SetUsageTemplate
函数自定义用法信息,如:
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()
}
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 标志,此标志的作用是输出应用的版本信息,如:
var rootCmd = &cobra.Command{
Use: "app",
Short: "An example app with Version and SetVersionTemplate",
Version: "1.0.0",
}
func main() {
_ = rootCmd.Execute()
}
go run main.go --version
#app version 1.0.0
自定义版本信息
我们可以使用 cmd.SetVersionTemplate(s string)
函数自定义版本信息模板,如:
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()
}
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
注意,若子命令没有自己的钩子函数,将会继承父命令的钩子函数
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()
}
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 将打印建议,建议是根据注册的子命令自动生成的,如:
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()
}
go run main.go f
#Error: unknown command "f" for "app"
#
#Did you mean this?
# foo
#
#Run 'app --help' for usage.
禁用建议
我们可以使用 command.DisableSuggestions = true
以禁用子命令建议,如:
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()
}
go run main.go f
#Error: unknown command "f" for "app"
#Run 'app --help' for usage.
我们还可以使用 SuggestFor
属性显式设置建议给定命令的名称,如:
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()
}
go run main.go qux
#Error: unknown command "qux" for "app"
#
#Did you mean this?
# bar
#
#Run 'app --help' for usage.
为命令生成文档
我们可以根据子命令、标志等生成以下格式的文档:
-
Markdown,如:
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,如:
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,如:
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) } }
本文由 Sue211213 原创,发表于 2023 年 8 月 13 日,转载请注明出处和作者,谢谢!