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 上。