Cobra 的介绍与使用
介绍
cobra是一个用来构建现代CLI工具的库。相比flag标准库,它提供更多方便的特性和功能。Cobra 由 Go 项目成员和 hugo 作者 spf13 创建,已经被许多流行的 Go 项目采用,比如 GitHub CLI 和 Docker CLI。
源码地址: [https://github.com/spf13/cobra],截止到2024-2-21
Star 35.3K
特性预览
- 使用cobra add cmdname可快速的创建子命令cli
 - 全局、局部和级联的标志
 - 自动生成commands和flags的帮助信息
 - 自动识别 -h、--help 等帮助标识
 - 支持自定义帮助信息,用法等的灵活性。
 - 可与 viper 紧密集成
 
相关概念
Cobra 结构由三部分组成:命令 (commands)、参数 (arguments)、标志 (flags)。最好的应用程序在使用时读起来像句子,要遵循的模式:
# 没有子命令
`app cmd --param=?`: 
# 有子命令
`app cmd subCmd --param=?`
app:代表编译后的文件名, cmd:代表命令 subCmd:代表子命令 --param: 代表请求参数。
安装
Cobra 可以使用以下命令进行安装:
$ go get -u github.com/spf13/cobra/cobra 
快速使用
快速创建一个cli,效果是app server --port=?运行一个服务。
创建根命令
package cmd
import "github.com/spf13/cobra"
var rootCmd = &cobra.Command{
	Use:   "app",
	Short: "命令行的简要描述",
	Long: `使用cobra开发cli命令,
-app: 指的是编译后的文件名`,
	// 根命令执行方法,需要就添加
	// Run: func(cmd *cobra.Command, args []string) {
	//
	// },
}
func init() {
	rootCmd.PersistentFlags().String("version", "", "版本")
}
func Execute() {
	cobra.CheckErr(rootCmd.Execute())
}
创建子命令
package cmd
import (
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
	"log"
)
var (
	// 接收端口号
	port string
	serverCmd = &cobra.Command{
		Use:   "server",
		Short: "启动http服务,使用方法: app server --port=?",
		Run: func(cmd *cobra.Command, args []string) {
			if port == "" {
				log.Fatalf("port参数不能为空")
			}
			engine := gin.Default()
			if err := engine.Run(":" + port); err != nil {
				log.Fatalf("服务器启动失败, err: %s", err.Error())
			}
		},
	}
)
func init() {
	// 将server命令添加为rootCmd的子命令
	rootCmd.AddCommand(serverCmd)
	// server子命令接收port选项参数
	serverCmd.Flags().StringVar(&port, "port", "", "端口号")
}
编译运行
(1) 编译
# 编译(编译后的文件名为: app)
$ go build -o app .
(2) 运行(不带参数)
[root@master demo01]# ./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.
(3)查看具体子命令
[root@master demo01]# ./app server -h
启动http服务,使用方法: app server --port=?
Usage:
  app server [flags]
Flags:
  -h, --help          help for server
      --port string   端口号
Global Flags:
      --version string   版本
(4) 执行子命令
# 不传必带参数时
[root@master demo01]# ./app server
2022/03/31 20:47:50 port参数不能为空
# 传参数
[root@master demo01]# ./app server --port=8080
[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] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
嵌套子命令
编辑命令
package cmd
import (
	"fmt"
	"github.com/spf13/cobra"
)
var (
	name string
	// userCmd 父命令
	userCmd = &cobra.Command{
		Use:   "user",
		Short: "用户操作",
	}
	// 添加用户子命令
	addUserCmd = &cobra.Command{
		Use:   "add",
		Short: "添加用户: user add --name=?",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("添加用户: ", name)
		},
	}
	// 删除用户子命令
	delUserCmd = &cobra.Command{
		Use:   "del",
		Short: "删除用户: user del --name=?",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("删除用户: ", name)
		},
	}
)
func init() {
	rootCmd.AddCommand(userCmd)
	userCmd.AddCommand(addUserCmd)
	userCmd.AddCommand(delUserCmd)
	// 用户命令接收参数
	userCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "用户名")
}
查看命令
[root@master demo02]# ./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   用户名
Global Flags:
--version string   版本
Use "app user [command] --help" for more information about a command.
执行命令
[root@master demo02]# ./app user add -n lgc
添加用户:  lgc
[root@master demo02]# ./app user del --name lgc
删除用户:  lgc
标志(flags)
cobra的标志有本地标志和持久化标志:
- 本地标志(Flags):当前命令接收,当前命令使用
 - 持久标志(PersistentFlags):当前命令参数接收,当前命令行和其所有子命令都可以使用
 
使用示例
package cmd
import (
	"fmt"
	"github.com/spf13/cobra"
)
var (
	name string
	list []string
	// userCmd 父命令
	userCmd = &cobra.Command{
		Use:   "user",
		Short: "用户操作",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("用户列表: ", list)
		},
	}
	// 添加用户子命令
	addUserCmd = &cobra.Command{
		Use:   "add",
		Short: "添加用户: user add --name=?",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("添加用户: ", name)
		},
	}
	// 删除用户子命令
	delUserCmd = &cobra.Command{
		Use:   "del",
		Short: "删除用户: user del --name=?",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("删除用户: ", name)
		},
	}
)
func init() {
	rootCmd.AddCommand(userCmd)
	userCmd.AddCommand(addUserCmd)
	userCmd.AddCommand(delUserCmd)
	// 父命令接收持久标志
	userCmd.PersistentFlags().StringVarP(&name, "name", "n", "", "用户名")
	// 父命令接收本地标志
	userCmd.Flags().StringSliceVarP(&list, "list", "l", []string{}, "用户列表")
}
运行测试
[root@master demo03]# ./app user --list "jack,lucy"
用户列表:  [jack lucy]
[root@master demo03]# ./app user add --list "jack,lucy"
Error: unknown flag: --list
Usage:
  app user add [flags]
Flags:
  -h, --help   help for add
Global Flags:
  -n, --name string      用户名
      --version string   版本
Error: unknown flag: --list
参数校验
非选项参数校验
使用内置函数:
- NoArgs : 如果有任何位置参数,该命令将报告错误。
 - MinimumNArgs(int) :至少传 N 个位置参数,否则报错。
 - ArbitraryArgs: 接受任意个位置参数。
 - MaximumNArgs(int) : 最多传N 个位置参数,否则报错。
 - ExactArgs(int) : 传入位置参数个数等于N,否则报错。
 - RangeArgs(min, max) : 传入位置参数个数 min<= N <= max,否则报错
 
示例代码:
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)
        },
    }
)
运行测试:
# 传入3个位置参数
➜  go-cli ./app user add 1 2 3      
位置参数(args): [1 2 3]
# 传入4个位置参数
➜  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 
使用自定义函数:
	// 删除用户子命令
	delUserCmd = &cobra.Command{
		Use:   "del",
		Short: "删除用户: user del --name=?",
		// 非选项参数校验方式2:自定义参数限制
		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)
		},
	}
选项参数校验
选项参数是可选的,如果你想在缺少标志时命令报错,可使用MarkFlagRequired限制:
func init() {
	// 添加子命令到父命令
	userCmd.AddCommand(userAddCmd)
	rootCmd.AddCommand(userCmd)
	// 标志
	userAddCmd.Flags().StringVar(&name,"name","","用户名")
	// 标志必需
	err := userAddCmd.MarkFlagRequired("name")
	if err != nil {
		fmt.Println("--name 不能为空")
		return
	}
}
运行测试:
# 不传--name标志
➜ ./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
# 传 --name 标志
➜ ./app user add --name=张三
name: 张三
集成viper
viper 的详细使用方法请查看[[Viper 的使用|这篇文章]]。
查看目录结构
├── app
│   └── config
│       ├── app.go # 配置结构体
│       └── app.yaml # 配置文件
├── cmd
│   ├── root.go # 根命令
│   └── server.go # http服务
├── go.mod
├── go.sum
├── local.yaml #
└── main.go
代码实现
解析配置:
package cmd
import (
	"fmt"
	"github.com/lgc202/go-example/cobra/demo05/app/config"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"os"
)
var (
	cfgFile   string            // 配置文件
	appConfig *config.AppConfig // 配置对应的结构体
	rootCmd = &cobra.Command{
		Use:   "",
		Short: "命令行的简要描述....",
		Long:  `学习使用Cobra,开发cli项目,app: 指的是编译后的文件名。`,
	}
)
func initConfig() {
	// 接收指定的配置文件
	if cfgFile != "" {
		// Use config file from the flag.
		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)
}
func init() {
	// 初始化配置信息
	cobra.OnInitialize(initConfig)
	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./app.yaml | ./config/app.yaml )")
}
func Execute() {
	cobra.CheckErr(rootCmd.Execute())
}
使用配置:
package cmd
import (
	"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)
		},
	}
)
func init() {
	// 添加命令
	rootCmd.AddCommand(serverCmd)
}
具体配置详情:
package config
type 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"`
}
app:
  version: v1.0.0
  author: lgc
  port: 8080
mysql:
  host: 127.0.0.1
  data_base: test
  user: root
  password: root
app:
  version: v1.0.2
  author: lgc
  port: 8081
mysql:
  host: 192.168.0.10
  data_base: test
  user: root
  password: root
编译运行
# 编译
➜ go build -o cli .
# 默认启动http
➜ ./cli server
&{App:{Version:v1.0.0 Author:刘庆辉 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:刘庆辉 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
最后,本文的源码存放在 github 上。
                    
                
                
            
        
浙公网安备 33010602011771号