简介
Cobra是一个库,提供了能够创建强大现代的CLI的接口。介绍的Viper是为了能够解决配置问题,其中包括命令行选项,是viper的一部分。
Cobra中是使用了pflag来解析命令行选项的,同样可以结合viper使用来读取命令行选项的配置。
Cobra提供了:
- 简单的基于子命令的CLI:
app server,app fetch等等。 - 完全符合POSIX的flags(包括short和long版本)。
- 嵌套子命令
- 全局,局部和级联的flags
- 智能建议(
app srverdid you meanapp server?) - 自动帮助生成命令和标志
- 自动帮助标志识别-h,-help,等。
- 为您的应用程序自动生成shell自动完成(bash, zsh, fish, powershell)
- 自动为你的应用生成帮助手册
- 命令别名可以让你更改而不会损坏内容
- 灵活定义help,usage等。
- 无缝集成viper
概念
Cobra是建立在command、argument和flags的结构上的。
- 命令是应用程序的中心点。应用程序支持的每个交互都包含在Command中。一个命令可以有子命令,也可以有选择地运行一个操作。
- 标志是修改命令行为的一种方式。Cobra完全支持posix兼容标志以及Go的flag包。Cobra命令可以定义贯穿子命令的标志,以及仅对该命令可用的标志。
安装
使用go get命令安装包
go get -u github.com/spf13/cobra@latest导入包后可以使用
import "github.com/spf13/cobra"cobra-cli
cobra-cli是一个命令行程序,用于生成cobra应用程序和命令文件。它将引导您的应用程序脚手架,以快速开发基于cobra的应用程序。这是将Cobra合并到您的应用程序中最简单的方法。
Cobra提供了自己的程序,可以创建您的应用程序并添加您想要的任何命令。这是将Cobra合并到应用程序中最简单的方法。
首先安装这个工具,会自动安装在$GOPATH/bin下
go install github.com/spf13/cobra-cli@latest$GOPATH/bin是添加到$PATH中的,这样安装后cobra-cli命令就可用了。
cobra-cli init
cobra-cli init [app]命令将会创建并初始化你的应用程序代码。这是个强大的应用,会创建正确的结构无需担心,还可以将你指定的许可证应用到你的应用程序。
随着go的模块的引入,Cobra也利用模块简化了生成器,cobra的生成器需要在模块内使用。如果没有模块则可以初始化一个模块,例如:
cd $HOME/code
mkdir myapp
cd myapp
go mod init github.com/spf13/myapp在模块内部执行cobra-cli init就会创建一个新的项目骨架,可供修改。可以直接go run main.go运行这个骨架。
cd $HOME/code/myapp
cobra-cli init
go run main.gocobra-cli还可以在子目录下运行,这可以方便将应用程序代码与库代码分离开。
可选的选项
--author提供作者名,例如cobra-cli init --author "Steve Francia [email protected]--license提供许可证,例如cobra-cli init --license apache--viper会自动安装viper
为项目添加命令
初始化cobra应用程序后,您可以继续使用cobra生成器向应用程序添加其他命令。执行此操作的命令是cobra-cli add。
如果要创建下面三个命令:
- app server
- app config
- app config create 在你的项目目录(main.go所在目录)执行下面命令:
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd'最后一条命令的-p选项是用来给新增选项分配父命令的,因为create是config的子命令。默认情况下所有命令都是rootCmd的子命令。
默认情况下,cobra-cli会为命令名扩展Cmd。命令的命名风格是驼峰式的。
应用结构如下:
▾ app/
▾ cmd/
config.go
create.go
serve.go
root.go
main.go现在,您已经有了一个基本的基于cobra的应用程序并在运行。下一步是在cmd中编辑文件,并为您的应用程序定制它们。详情可以看下一节中对库的使用介绍。
配置cobra生成器
提供一个配置文件,可以让cobra生成器更加易用,可以用配置中的选项替代输入命令行选项。
~/.cobra.yaml文件示例:
author: Steve Francia <[email protected]>
license: MIT
useViper: true用户也可以指定自定义的许可证:
author: Steve Francia <[email protected]>
year: 2020
license:
header: This file is part of CLI application foo.
text: |
{{ .copyright }}
This is my license. There are many like it, but this one is mine.
My license is my best friend. It is my life. I must master it as I must
master my life.其中的copyright会根据author和year生成。header可以用作许可头文件,例如一个go文件中:
/*
Copyright © 2020 Steve Francia <[email protected]>
This file is part of CLI application foo.
*/下面介绍cobra库的使用。
cobra-library
虽然可以提供你自己的组织结构,但是cobra的项目应用都遵循下面的结构:
▾ appName/
▾ cmd/
add.go
your.go
commands.go
here.go
main.go在一个cobra应用中,main.go文件是非常简单的,它的目的就只有一个,即初始化cobra。
package main
import (
"{pathToYourApp}/cmd"
)
func main() {
cmd.Execute()
}初始化项目
首先需要创建一个go项目。具有一个简短的main.go和一个rootCmd文件。即
app/
/cmd/
root.go
main.go其中main.go就是上面所说的样子。
创建rootCmd
在app/cmd/下创建root.go,创建命令即可。
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at https://gohugo.io/documentation/`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}在init中定义选项并处理配置。后缀有Var表示绑定到某个变量,后缀有P表示有短标志。
func init() {
// Execute会回调initConfig函数
cobra.OnInitialize(initConfig)
// 有4个选项,利用cobra内置的pflag来解析命令行选项
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution")
rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project")
rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
// 将命令行选项与viper结合读取配置
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
viper.SetDefault("license", "apache")
//添加子命令
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(initCmd)
}cobra中的pflags可以用来解析命令行选项,viper也可以借此获得命令行选项的配置。initConfig就是用来初始化配置的,
func initConfig() {
//选项设置了配置文件则使用选项的配置文件,选项中没有配置则使用默认配置文件
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".cobra")
}
// 环境变量中有匹配已注册的键的,可以加载到viper中
viper.AutomaticEnv()
// 读取配置
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}添加子命令
在上面的代码中,使用了AddCommand添加子命令,其实也可以在子命令对应的文件的init中将自己加入rootCmd,如:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
}如果希望向命令的调用者返回一个错误,那么使用RunE
func init() {
rootCmd.AddCommand(tryCmd)
}
var tryCmd = &cobra.Command{
Use: "try",
Short: "Try and possibly fail at something",
RunE: func(cmd *cobra.Command, args []string) error {
if err := someFunc(); err != nil {
return err
}
return nil
},
}结合Flags
Flags提供了修饰符来控制命令的运行方式。
有两种方式来分配flag。
- persistent flags。这种类型的标志对命令和其子命令都是可用的。全部标记放在rootCmd下。
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")- local flags。这种类型标志只能在本命令中使用。
localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")默认情况下,cobra只解析目标命令的局部命令,忽略父命令的局部标志。通过启用command.traversechildren,COBRA将在执行目标命令之前在每个命令上解析本地标志。
command := cobra.Command{
Use: "print [OPTIONS] [COMMANDS]",
TraverseChildren: true,
}结合viper可以将从选项读取配置,可以注意,选项的配置的优先级比配置文件高
var author string
func init() {
rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}必要选项
选项默认都是可选的,如果希望选项没有设置的时候会报错,需要把选项标记为必选项。
rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkFlagRequired("region")persistent flags同理。
rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)")
rootCmd.MarkPersistentFlagRequired("region")选项组
必须需要一起提供的选项,例如--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")如果选项互斥,不能同时提供,例如指定输出格式--json或者--yaml,通过下面的方式设置。
rootCmd.Flags().BoolVar(&u, "json", false, "Output in JSON")
rootCmd.Flags().BoolVar(&pw, "yaml", false, "Output in YAML")
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml")位置和自定义参数
位置参数的验证可以使用Command的Args字段指定。 下面是内置的验证器:
NoArgs:如果有任何位置参数,命令将会报告一个错误。ArbitraryArgs:命令会接收任何参数OnlyValidArgs:如果存在不在ValidArgs字段中的参数,命令就会报错。MinimumNArgs(int):如果没有至少N个位置参数,该命令将报告错误。MaximumNArgs(int): 如果位置参数超过N,该命令将报告错误。ExactArgs(int):如果没有确切的N个位置参数,该命令将报告一个错误。ExactValidArgs(int):如果没有确切的N个位置参数,或者有任何位置参数不在command的ValidArgs字段中,该命令将报告一个错误RangeArgs(min, max):如果参数的数量不在预期参数的最小和最大数量之间,该命令将报告一个错误。MatchAll(pargs ...PositionalArgs): 允许将现有的检查与任意的其他检查相结合(例如,你想要检查ExactArgs的长度和其他质量)。
一个自定义验证器的例子如下:
var cmd = &cobra.Command{
Short: "hello",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires a color argument")
}
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!")
},
}help指令
当您有子命令时,Cobra会自动向您的应用程序添加help命令。此外,help还将支持所有其他命令作为输入。例如,您有一个名为“create”的命令,没有任何额外的配置。当“app help create”被调用时,Cobra将会工作。每个命令都会自动添加’——help标志。
默认的help命令没有具体的逻辑或行为。也可以自定义你想要的help命令。
自定义help
使用下面的函数来为默认命令提供自己的help命令和自己的模板。
cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)后两个会应用到所有的子命令。
Usage信息
当用户提供无效标志或无效命令时,Cobra会向用户显示“Usage”。 例如下面的例子:
$ cobra --invalid
Error: unknown flag: --invalid
Usage:
cobra [command]
Available Commands:
add Add a command to a Cobra Application
help Help about any command
init Initialize a Cobra Application
Flags:
-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
-l, --license string name of license for the project
--viper use Viper for configuration (default true)
Use "cobra [command] --help" for more information about a command.也可以自定义Usage函数和模板,就像help命令一样,通过下面的函数
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)Version选项
如果在rootCmd中设置了version字段,Cobra会添加一个顶级的--version标志。运行带有--version标志的应用程序将使用版本模板将版本打印到标准输出。模板可以通过cmd.SetVersionTemplate(s string)函数定制。
PreRun和PostRun Hooks
可能需要在命令的Run函数执行前或者后执行某些函数。PersistentPreRun和PreRun函数会在Run之前执行。PersistentPostRun和PostRun会在Run之后运行。Persistent*Run如果子命令没有声明会继承父命令的。执行的先后顺序如下:
PersistentPreRunPreRunRunPostRunPersistentPostRun
下面是使用所有这些特性的两个命令示例。当执行子命令时,它将运行root命令的PersistentPreRun,而不是root命令的PersistentPostRun。因为子命令没有声明PersistentPreRun,会继承父命令的,而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()
}未知命令建议
当输入的命令无法识别,产生”unknown commands”错误,会给出建议的命令。
建议是自动基于每个注册的子命令和使用Levenshtein距离的实现。每个匹配最小距离为2(忽略大小写)的注册命令将作为建议显示。
可以禁用此功能,也可以调整距离
// 禁用
command.DisableSuggestions = true
// 调整最小距离
command.SuggestionsMinimumDistance = 1您还可以使用SuggestFor属性显式地设置指定命令的名称。这允许对字符串距离不接近的字符串提出建议,例如为remote建议delete,虽然两个单词距离不接近,但是仍然很有意义。
$ kubectl remove
Error: unknown command "remove" for "kubectl"
Did you mean this?
delete
Run 'kubectl help' for usage.为命令生成文档
cobra可以基于子命令,flags等等来生成文档。生成的文档中会带有”Auto generated by spf13/cobra…”这样的句子,可以设置cmd.DisableAutoGenTag = true来移除这些句子。
生成Man Pages
设置root命令和man page头,指定生成路径即可,例如:
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
header := &doc.GenManHeader{
Title: "MINE",
Section: "3",
}
err := doc.GenManTree(cmd, header, "/tmp")
if err != nil {
log.Fatal(err)
}
}生成Markdown文档
使用方法差不多一致。
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenMarkdownTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}生成ReStructured Text文档
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenReSTTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}生成YAML文档
package main
import (
"log"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
err := doc.GenYamlTree(cmd, "/tmp")
if err != nil {
log.Fatal(err)
}
}生成shell补全
Cobra可以为以下shell生成一个shell完成文件:bash、zsh、fish、PowerShell。这样只需要输入部分命令就可以输入tab获得匹配的完整命令。