Cobra 的介绍与使用

介绍

cobra是一个用来构建现代CLI工具的库。相比flag标准库,它提供更多方便的特性和功能。Cobra 由 Go 项目成员和 hugo 作者 spf13 创建,已经被许多流行的 Go 项目采用,比如 GitHub CLIDocker 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 上。

posted @ 2024-03-11 17:08  lgc202  阅读(378)  评论(0编辑  收藏  举报