×错例:
不能完全代替shell,cobra能支持的脚本格式比较局限
无法像Java -jar -Dxx=str1 -Dxx2=str2 那样读取-D指示的值
读取参数只能为无格式纯文本,不要尝试读取json、特殊字符、转义符和富文本。
避免一大堆可变配置参数都通过flag传,参数太多建议改成读取配置文件
√正确:
读取5个以上的flag时使用,配置文件+viper,环境变量等,减少flag的个数到5个以内;
参数的值不要过长,仅纯文本string、int和bool类型,富文本、json等建议通过flag传文件路径,在run方法的逻辑读取文件,进行操作
短平快、小型脚手架推荐使用cobra封装命令集,同时学习了解pflag、flag、viper库
cobra既是一个用于创建强大现代CLI应用程序的库,也是一个生成应用程序和命令文件的程序。cobra被用在很多go语言的项目中,比如 Kubernetes、Docker、Istio、ETCD、Hugo、Github CLI等等,更多项目详见此列表。
cobra由命令、参数、标志组成
commands代表动作,args是事物,flags是动作的修饰符(一般两个-连接符)
模式如下:
APPNAME VERB NOUN --ADJECTIVE. or APPNAME COMMAND ARG --FLAG(APPNAME 动词 名词 形容词 或者 APPNAME 命令 参数 标志)
如下的例子,server 是command,port是flag
hugo server --port=1313
命令是交互程序的中心,每一个命令都有子命令。
链接:cobra package - github.com/spf13/cobra - Go Packages
需要rootCmd和其他命令(按需)
flag是一种修改命令行为的方式,cobra支持完全兼容POSIX标志,也支持go flag package,cobra可以定义到子命令上的标志,也可以仅对该命令可用的标志
args参数是命令command需要识别的一些值,cobra可以指定读取任意数量的参数,也可以不接受参数,此时该输入会被当作未定义的command报错。
command、args搭配使用
参考:Go语言命令行利器cobra使用教程 - 简书
main.go负责调用rootCmd execute方法
import "cobra_cmd/cmd"
func main() {
cmd.Execute()
}
在cmd目录下创建rootcmd.go
//每一个command命令,都是一个 &cobra.Command{
} 示例
var rootCmd = &cobra.Command{
Use: "root-cli",
Short: "示例脚本 root cli",
Long: `这是 cobra 测试程序使用的示例脚本,您可参考cobra其他文档并随时更新本demo`,
//参数验证器,设置某个脚本
能接受参数的个数。
Args: cobra.MinimumNArgs(1),
RunE: runRoot, //RunE Run字段都是执行具体的函数
,抽到别处定义
}
//rootCmd.Execute() 是命令执行入口
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
//具体的执行逻辑
func runRoot(cmd *cobra.Command, args []string) error {
fmt.Printf("execute %s args:%v \n", cmd.Name(), args)
//例如 这里处理无参数启动时程序处理
if len(args) < 1 {
return errors.New("requires at least one arg")
}
// do something.
return nil
}
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
//定义 标志
var echoTimes int
func init() {
//通过AddCommand组成父子关系,多级命令
rootCmd.AddCommand(helloCmd)
// root有两个子命令,和一个cmdEcho命令下的子命令cmdTimes
//一级root --> 二级print,echo --> 三级times
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
//本地标志,只在mdTimes命令
起作用
cmdTimes.Flags().IntVarP(&echoTimes, "echoTimes", "t", 1, "times to echo the input")
}
以上是一个入门demo,演示了命令command结构、标志的定义和读取、命令组成上下级、父子关系。
重点:
1.rootCmd和其他command放 cmd 包下;
2.在单独的commandXxx.go文件里,为每一个command定义 &cobra.Command{} 结构体并在init()方法里通过AddCommand() 做父子上下级关系关联、以及flag标志的读取操作;
3.main()入口方法调用到rootCmd.Execute()即完成工作。
结构如下,列举大概常用的字段:
&cobra.Command{
//
命令名称
Use: "echo [string to echo]",
//
命令别名
Aliases: []string{"log","output"},
//
命令简介
Short: "Echo anything to the screen",
//
命令详细说明
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
package main
import (
"bufio"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)
var rootCmd = &cobra.Command{
Use: "demo",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
//运行命令的逻辑
//Run: func(cmd *cobra.Command, args []string) {
//debug
//fmt.Println("== root命令运行成功 ==")
//},
}
// 再定义二级子命令:
var createCmd = &cobra.Command{
Use: "create",
Short: "创建书签",
}
var urlCmd = &cobra.Command{
Use: "url",
Short: "Bookmark a url link ",
Long: "收藏一个url链接",
//cobra 常用的参数配置校验器如下:
//
//MinimumNArgs(int) 当参数数目低于配置的最小参数个数时报错
//MaximumNArgs(int) 当参数数目大于配置的最大参数个数时报错
//ExactArgs(int) 如果参数数目不是配置的参数个数时报错
//NoArgs 没有参数则报错
//参数验证除了RangeArgs 还有 NoArg、ExactArgs、
Args: cobra.RangeArgs(1, 3),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("link is:", link)
if len(args) == 2 {
fmt.Println(args[0], args[1])
} else if len(args) == 0 {
fmt.Println("收藏链接参数:", args)
return
} else {
fmt.Println("收藏链接参数:", args)
}
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
}
//及时关闭file句柄
defer file.Close()
//写入文件时,使用带缓存的 *Writer
write := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
write.WriteString("link \n")
}
//Flush将缓存的文件真正写入到文件中
write.Flush()
},
}
var link string
var filePath string
// init 方法完成父子关系绑定,标志的初始化设置以及其他初始化工作
func init() {
rootCmd.AddCommand(createCmd)
createCmd.AddCommand(urlCmd)
//持久标志,传递给所有子命令
rootCmd.PersistentFlags().StringVarP(&filePath, "path", "p", "./bookmarks.txt", "save to txt file")
//局部标志,仅用于具体一个子命令
urlCmd.Flags().StringVarP(&link, "link", "l", "", "收藏链接格式: 链接地址 名称")
//钩子函数演示
rootCmd.AddCommand(hookCmd)
hookCmd.AddCommand(hookSub)
hookSub.AddCommand(hookfinal)
}
func main() {
// For environment variables.
viper.SetEnvPrefix("core")
viper.AutomaticEnv()
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// / 钩子函数测试
var hookCmd = &cobra.Command{
Use: "hookroot",
Short: "钩子函数测试 一级",
Long: "测试 生命周期的不同钩子函数的继承关系",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hookroot: PersistentPreRun")
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hookroot: PreRun")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hookroot: Running...")
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hookroot: PostRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hookroot: PersistentPostRun")
},
}
// // 会继承 父命令的PersistentPreRun、PersistentPostRun
var hookSub = &cobra.Command{
Use: "hooksub",
Short: "钩子函数测试 二级",
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hooksub: PreRun")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hooksub: Running...")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hooksub: PersistentPostRun") //覆盖了root的PersistentPostRun,并传给下级
},
}
// // 会继承 父命令的PersistentPreRun、PersistentPostRun
var hookfinal = &cobra.Command{
Use: "hookfinal",
Aliases: []string{"final", "finally"},
Short: "钩子函数测试 三级",
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("hookfinal: PreRun")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hookfinal: Running...")
},
}
output:
//go run .\helloworld.go hookroot hooksub hookfinal
//hookroot: PersistentPreRun
//hookfinal: PreRun
//hookfinal: Running...
//hooksub: PersistentPostRun/
以上是一个进阶demo,演示了PersistentFlags和Flags的区别,以及钩子函数在向下传递时可被覆盖
重点:
1.PersistentFlags 设置的是持久标志,由rootCmd根命令设置,可被其他命令使用,而flag设置的只能在当前命令使用;
2.在单独的command执行之前、之后都可以执行其他操作,也即预先处理、后置处理。执行顺序为PersistentPreRun、PreRun、Run、PostRun、PersistentPostRun
3.带Persistent的可以传递给子命令(继承),但也可以被子命令重新声明而被覆盖(重写)
4.不带Persistent开头的前后处理
以下代码:Args: cobra.MinimumNArgs(1)就是一个验证方法
import (
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// Do something here
},
}
func init() {
// Here you will define your flags and configuration settings.
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
可以看到在 rootCmd 中设置了 Args: cobra.MinimumNArgs(1),这将强制要求至少传入一个参数,否则会抛出一个错误。此外,还可以使用 cobra 提供的其他参数验证方法,例如:
cobra.MaximumNArgs(n int):强制要求不超过 n 个参数。
cobra.OnlyValidArgs([]string):强制要求传入的参数必须是所指定的 string 类型的 slice 中的值。
cobra.ArgRange(min, max int):强制要求传入参数的数量在 min 和 max 之间。
以上是一些常用的参数验证方法,Cobra 还有更多的验证方法和选项,可以根据实际需求进行选择和设置。
参考:
Go 每日一库之 cobra - 知乎
Cobra 中文文档 - 掘金
可以在执行命令之前和之后运行一个函数。PersistentPreRun 和 PreRun 函数将在 Run 之前执行。PersistentPostRun 和 PostRun 会在 Run 之后运行。如果子级未声明自己的 Persistent * Run 函数,则子级将继承父级的。这些函数的执行顺续如下:
PersistentPreRun
PreRun
Run
PostRun
PersistentPostRun
PersistentXxx是可以被子命令继承的前、后置处理
当你添加了子命令,Cobra 会自动添加一些帮助命令。当你执行 app help 命令时会显示帮助信息。另外,help 还支持其他命令作为输入参数。举例来说,你有一个没有额外配置的 create 命令,app help create 是有效的。每一个命令还会自动获取一个 --help 标志。
help 就像其他命令一样。并没有特殊的逻辑或行为。实际上,你可以根据需要提供自己的服务。
定义你自己的 help
你可以使用下面的方法提供你自己的 Help 命令或模板。
cmd.SetHelpCommand(cmd *Command)
cmd.setHelpCommand(f func(*Command, []string))
cmd.setHelpTemplate(s string)
后两者也适用于所有子命令。
当用户提供无效的标志或无效的命令时,Cobra 会通过向用户显示 usage 进行响应。
这时可以自定义usage用法,提供自己的 usage函数和模板
cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)
可以参考 GitHub CLI 项目中usage使用信息的写法。
参考cobra document#mrkdown-docs
cobra支持生成所有命令的帮助文档树doc.GenMarkdownTree(cmd,dir)
main.go:
import (
"github.com/spf13/cobra/doc"
"log"
"os"
)
func InitCommand() {
rootCmd.AddCommand(helloCmd)
// 两个顶层的命令,和一个cmdEcho命令下的子命令cmdTimes
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
//utils.Mkdir("./docs")
os.MkdirAll("H:\\docs", os.ModePerm) //显式创建目标文件夹,文件夹存在时再次调用不会做任何事情
err := doc.GenMarkdownTree(rootCmd, "H:\\docs") //核心api GenMarkdownTree
if err != nil {
log.Fatal(err)
}
}
给某个命令及其子命令生成帮助文档树(一系列文档),参考以下代码片段:
err := doc.GenMarkdownTree(rootCmd, "H:\\docs") //核心api GenMarkdownTree
if err != nil {
log.Fatal(err)
}
其他高级用法或生成其他格式帮助文档,请参考cobra document#mrkdown-docs ,以及cobra代码库的github.com/spf13/cobra/doc文件夹下的代码和文档。