介绍 cobra
是一个用来构建现代CLI工具的库。相比flag
标准库,它提供更多方便的特性和功能。Cobra
由 Go
项目成员和 hugo
作者 spf13 创建,已经被许多流行的 Go
项目采用,比如 GitHub CLI 和 Docker CLI 。
源码地址: https://github.com/spf13/cobra,截止到现在`Star 23.8K`
特性预览
使用cobra add cmdname
可快速的创建子命令cli
全局、局部和级联的标志
自动生成commands
和flags
的帮助信息
自动识别 -h
、--help
等帮助标识
支持自定义帮助信息,用法等的灵活性。
可与viper 紧密集成
相关概念 Cobra
结构由三部分组成:命令 (commands
)、参数 (arguments
)、标志 (flags
)。最好的应用程序在使用时读起来像句子,要遵循的模式:
1 2 3 4 `app cmd --param=?`: `app cmd subCmd --param=?`
app
:代表编译后的文件名, cmd
:代表命令 subCmd
:代表子命令 --param
: 代表请求参数。
安装 1 go get -u github.com/spf13/cobra/cobra
快速使用
快速创建一个cli
,效果是app server --port=?
运行一个服务
创建根命令(rootCmd
) 文件位置: cmd/root.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package cmdimport ( "github.com/spf13/cobra" ) func init () { rootCmd.PersistentFlags().String("version" , "" , "版本" ) } var rootCmd = &cobra.Command{ Use: "app" , Short: "命令行的简要描述...." , Long: `学习使用Cobra,开发cli项目, - app: 指的是编译后的文件名。` , } func Execute () { cobra.CheckErr(rootCmd.Execute()) }
创建子命令 创建文件:cobra add ?
1 2 3 ➜ cobra add server server created at /Users/liuqh/ProjectItem/GoItem/go-cli
查看创建子命令内容 文件位置: cmd/server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package cmdimport ( "fmt" "github.com/spf13/cobra" ) var serverCmd = &cobra.Command{ Use: "server" , Short: "A brief description of your command" , Long: `A longer description that spans multiple lines and likely contains examples and usage of using your command. 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 ) { fmt.Println("server called" ) }, } func init () { rootCmd.AddCommand(serverCmd) }
编辑子命令内容 文件位置: cmd/server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package cmdimport ( "fmt" "github.com/gin-gonic/gin" "github.com/spf13/cobra" "os" ) var ( serverCmd = &cobra.Command{ Use: "server" , Short: "启动http服务,使用方法: app server --port=?" , Run: func (cmd *cobra.Command, args []string ) { if port == "" { fmt.Println("port不能为空!" ) os.Exit(-1 ) } engine := gin.Default() _ = engine.Run(":" + port) }, } port string ) func init () { rootCmd.AddCommand(serverCmd) serverCmd.Flags().StringVar(&port, "port" , "" , "端口号" ) }
编译运行 编译
运行(不带参数) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ➜ ./app 学习使用Cobra,开发cli项目, - app: 指的是编译后的文件名。 Usage: app [command ] Available Commands: completion generate the autocompletion script for the specified shell help Help about any command server 启动http服务,使用方法: app server --port=? Flags: -h, --help help for app --version string 版本 Use "app [command] --help" for more information about a command .
查看具体子命令 1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ ./app server -h 启动http服务,使用方法: app server --port=? Usage: app server [flags] Flags: -h, --help help for server --port string 端口号 Global Flags: --version string 版本
运行子命令 1 2 3 4 5 6 7 8 9 10 11 12 ➜ ./app server port不能为空! ➜ ./app server --port 8090 [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env : export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] Listening and serving HTTP on :8090
嵌套子命令 编辑命令 文件位置: cmd/user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package cmdimport ( "fmt" "github.com/spf13/cobra" ) var userCmd = &cobra.Command{ Use: "user" , Short: "用户操作" , } var userAddCmd = &cobra.Command{ Use: "add" , Short: "添加用户;user add --name=?" , Run: func (cmd *cobra.Command, args []string ) { fmt.Println("添加用户:" , name) }, } var userDelCmd = &cobra.Command{ Use: "del" , Short: "删除用户;user del --name=?" , Run: func (cmd *cobra.Command, args []string ) { fmt.Println("删除用户:" , name) }, } var name string func init () { userCmd.AddCommand(userDelCmd) userCmd.AddCommand(userAddCmd) rootCmd.AddCommand(userCmd) userCmd.PersistentFlags().StringVarP(&name, "name" , "n" , "" , "用户名" ) }
查看命令 1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ ./app user -h 用户操作 Usage: app user [command ] Available Commands: add 添加用户;user add --name=? del 删除用户;user del --name=? Flags: -h, --help help for user -n, --name string 用户名
编译运行 1 2 3 4 5 6 7 8 ➜ go build -o app . ➜ ./app user add -n 张三 添加用户: 张三 ➜ ./app user del -n 张三 删除用户: 张三
标志 (flags
) cobra
的标志指的就是参数的名称,但是有本地标志 和持久化标志 之分,白话描述:
本地标志(Flags
): 当前命令接收,当前命令使用。
持久标志(PersistentFlags
): 当前命令接收,当前命令行和其所有子命令都可使用。
使用示例
修改用户操作命令 cmd/user.go
修改后的脚本 文件位置: cmd/user.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package cmdimport ( "fmt" "github.com/spf13/cobra" ) var userCmd = &cobra.Command{ Use: "user" , Short: "用户操作" , Run: func (cmd *cobra.Command, args []string ) { fmt.Println("用户列表: " , list) }, } var userAddCmd = &cobra.Command{ Use: "add" , Short: "添加用户;user add --name=?" , Run: func (cmd *cobra.Command, args []string ) { fmt.Println("添加用户:" , name) }, } var userDelCmd = &cobra.Command{ Use: "del" , Short: "删除用户;user del --name=?" , Run: func (cmd *cobra.Command, args []string ) { fmt.Println("删除用户:" , name) }, } var ( name string list []string ) func init () { userCmd.AddCommand(userDelCmd) userCmd.AddCommand(userAddCmd) rootCmd.AddCommand(userCmd) userCmd.PersistentFlags().StringVarP(&name, "name" , "n" , "" , "用户名" ) userCmd.Flags().StringSliceVarP(&list, "list" , "l" , []string {}, "用户列表" ) }
运行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ➜ ./app user --list="小明,小三" 用户列表: [小明 小三] ➜ ./app user del --list="小明,小三" Error: unknown flag: --list Usage: app user del [flags] Flags: -h, --help help for del Global Flags: -n, --name string 用户名 Error: unknown flag: --list
参数限制 位置参数限制
NoArgs
: 如果有任何位置参数,该命令将报告错误。
MinimumNArgs(int)
:至少传 N 个位置参数,否则报错。
ArbitraryArgs
: 接受任意个位置参数。
MaximumNArgs(int)
: 最多传N 个位置参数,否则报错。
ExactArgs(int)
: 传入位置参数个数等于N,否则报错。
RangeArgs(min, max)
: 传入位置参数个数 min<= N <= max
,否则报错
代码示例 1 2 3 4 5 6 7 8 9 var userAddCmd = &cobra.Command{ Use: "add" , Short: "添加用户;user add --name=?" , Args: cobra.RangeArgs(1 , 3 ), Run: func (cmd *cobra.Command, args []string ) { fmt.Println("位置参数(args):" , args) }, }
@注意:上面规则限制的是位置参数,并不是标志,不要混淆。
运行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 ➜ go-cli ./app user add 1 2 3 位置参数(args): [1 2 3] ➜ go-cli ./app user add 1 2 3 4 Error: accepts between 1 and 3 arg(s), received 4 Usage: app user add [flags] Flags: -h, --help help for add Error: accepts between 1 and 3 arg(s), received 4
标志参数限制 标志默认是可选的。如果你想在缺少标志时命令报错,可使用MarkFlagRequired
限制
代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 func init () { userCmd.AddCommand(userAddCmd) rootCmd.AddCommand(userCmd) userAddCmd.Flags().StringVar(&name, "name" , "" , "用户名" ) err := userAddCmd.MarkFlagRequired("name" ) if err != nil { fmt.Println("--name 不能为空" ) return } }
运行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ➜ ./app user add Error: required flag(s) "name" not set Usage: app user add [flags] Flags: -h, --help help for add --name string 用户名 Error: required flag(s) "name" not set ➜ ./app user add --name=张三 name: 张三
自定义位置限制 代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package cmdimport ( "errors" "fmt" "github.com/spf13/cobra" "unicode/utf8" ) var userCmd = &cobra.Command{ Use: "user" , Short: "用户操作" , } var ( userAddCmd = &cobra.Command{ Use: "add" , Short: "添加用户;user add name" , Args: func (cmd *cobra.Command, args []string ) error { if len (args) != 1 { return errors.New("参数数量不对" ) } count := utf8.RuneCountInString(args[0 ]) fmt.Printf("%v %v \n" , args[0 ], count) if count > 4 { return errors.New("姓名长度过长" ) } return nil }, Run: func (cmd *cobra.Command, args []string ) { fmt.Println("args:" , args) }, } ) func init () { userCmd.AddCommand(userAddCmd) rootCmd.AddCommand(userCmd) }
运行测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ➜ ./app user add 上官伊人 上官伊人 4 args: [上官伊人] ➜ ./app user add 上官伊人家 上官伊人家 5 Error: 姓名长度过长 Usage: app user add [flags] Flags: -h, --help help for add Error: 姓名长度过长 ➜ ./app user add 上官伊人家 小三 Error: 参数数量不对 Usage: app user add [flags] Flags: -h, --help help for add Error: 参数数量不对
集成viper 查看目录结构 1 2 3 4 5 6 7 8 9 10 11 ├── app │ └── config │ ├── app.go │ └── app.yaml ├── cmd │ ├── root.go │ └── server.go ├── go.mod ├── go.sum ├── local.yaml └── main.go
代码实现 解析配置: go-cli/cmd/root.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package cmdimport ( "shershon1991/go-cli/app/config" "fmt" "github.com/spf13/cobra" "github.com/spf13/viper" "os" ) func init () { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config" , "" , "config file (default is ./app.yaml | ./config/app.yaml )" ) } var rootCmd = &cobra.Command{ Use: "" , Short: "命令行的简要描述...." , Long: `学习使用Cobra,开发cli项目,app: 指的是编译后的文件名。` , } var ( cfgFile string appConfig *config.AppConfig ) func Execute () { cobra.CheckErr(rootCmd.Execute()) } func initConfig () { if cfgFile != "" { viper.SetConfigFile(cfgFile) } else { viper.AddConfigPath("." ) viper.AddConfigPath("./config" ) viper.AddConfigPath("./app/config" ) viper.SetConfigType("yaml" ) viper.SetConfigName("app" ) } viper.AutomaticEnv() if err := viper.ReadInConfig(); err != nil { fmt.Printf("viper.ReadInConfig: %v\n" , err) } err := viper.Unmarshal(&appConfig) if err != nil { fmt.Println(err) os.Exit(1 ) return } fmt.Printf("%+v\n" , appConfig) }
使用配置:go-cli/cmd/server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package cmdimport ( "fmt" "github.com/gin-gonic/gin" "github.com/spf13/cobra" "os" ) var ( serverCmd = &cobra.Command{ Use: "server" , Short: "启动http服务,使用方法: app server?" , Run: func (cmd *cobra.Command, args []string ) { if appConfig.App.Port == "" { fmt.Println("port不能为空!" ) os.Exit(-1 ) } engine := gin.Default() _ = engine.Run(":" + appConfig.App.Port) }, } port string ) func init () { rootCmd.AddCommand(serverCmd) serverCmd.Flags().StringVar(&port, "port" , "" , "端口号" ) }
具体配置详情 go-cli/app/config/app.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package configtype AppConfig struct { App app `yaml:"app"` MySql mysql `yaml:"mysql"` } type app struct { Version string `yaml:"version"` Author string `yaml:"author"` Port string `yaml:"port"` } type mysql struct { Host string `yaml:"host"` DataBase string `yaml:"data_base"` User string `yaml:"user"` Password string `yaml:"password"` }
go-cli/app/config/app.yaml
1 2 3 4 5 6 7 8 9 app: version: v1.0.0 author: Shershon port: 8080 mysql: host: 127.0 .0 .1 data_base: test user: root password: root
go-cli/local.yaml
1 2 3 4 5 6 7 8 9 app: version: v1.0.2 author: Shershon port: 8081 mysql: host: 192.168 .0 .10 data_base: test user: root password: root
编译运行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 ➜ go build -o cli . ➜ ./cli server &{App:{Version:v1.0.0 Author:Shershon Port:8080} MySql:{Host:127.0.0.1 DataBase: User:root Password:root}} [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env : export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] Listening and serving HTTP on :8080 ➜ ./cli server --config=./local.yaml &{App:{Version:v1.0.2 Author:Shershon Port:8081} MySql:{Host:192.168.0.10 DataBase: User:root Password:root}} [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env : export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] Listening and serving HTTP on :8081