Go 常用包: 構建現代 CLI 應用庫 Cobra

  1. 介紹

cobra是一個用來構建現代 CLI 工具的庫。相比flag標準庫,它提供更多方便的特性和功能。CobraGo 項目成員和 hugo 作者 spf13 創建,已經被許多流行的 Go 項目採用,比如 GitHub CLI 和 Docker CLI。

源碼地址: https://github.com/spf13/cobra,截止到現在Star 23.8K

1.1 特性預覽

1.2 相關概念

Cobra 結構由三部分組成:命令 (commands)、參數 (arguments)、標誌 (flags)。最好的應用程序在使用時讀起來像句子,要遵循的模式:

# 沒有子命令
`app cmd --param=`# 有子命令
`app cmd subCmd --param=`

app: 代表編譯後的文件名, cmd: 代表命令  subCmd: 代表子命令 --param: 代表請求參數。

  1. 安裝

go get -u github.com/spf13/cobra/cobra
  1. 快速使用

快速創建一個cli, 效果是app server --port=?運行一個服務

3.1 創建根命令 (rootCmd)

文件位置:cmd/root.go

package cmd

import (
 "github.com/spf13/cobra"
)

func init() {
 // 接受參數
 rootCmd.PersistentFlags().String("version""""版本")
}

// rootCmd represents the base command when called without any subcommands
// 根命令
var rootCmd = &cobra.Command{
 Use:   "app",
 Short: "命令行的簡要描述....",
 Long: `學習使用Cobra,開發cli項目,
- app: 指的是編譯後的文件名。`,
   //// 根命令執行方法,如果有要
 //Run: func(cmd *cobra.Command, args []string) {
 // fmt.Println("args:",args)
 //},
}

// Execute 將所有子命令添加到root命令並適當設置標誌。
// 這由 main.main() 調用。它只需要對 rootCmd 調用一次。
func Execute() {
 cobra.CheckErr(rootCmd.Execute())
}

3.2 創建子命令

1. 創建文件:cobra add ?

# 創建子命令server
➜ cobra add server   
server created at /Users/liuqh/ProjectItem/GoItem/go-cli

2. 查看創建子命令內容

文件位置:cmd/server.go

package cmd

import (
 "fmt"
 "github.com/spf13/cobra"
)
// serverCmd represents the server command
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)
}

3. 編輯子命令內容

文件位置:cmd/server.go

package cmd

import (
 "fmt"
 "github.com/gin-gonic/gin"
 "github.com/spf13/cobra"
 "os"
)
// serverCmd represents the server command
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)
 // 接收參數port
 serverCmd.Flags().StringVar(&port, "port""""端口號")
}

3.3 編譯運行

1. 編譯

# 編譯(編譯後的文件名爲: app)
➜ go build -o app .

2. 運行 (不帶參數)

➜ ./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. 查看具體子命令

# 查看server使用信息
➜ ./app server -h
啓動http服務,使用方法: app server --port=?

Usage:
  app server [flags]

Flags:
  -h, --help          help for server
      --port string   端口號

Global Flags:
      --version string   版本

4. 運行子命令

# 不傳必填參數時
➜ ./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
  1. 嵌套子命令

4.1 編輯命令

文件位置:cmd/user.go

package cmd

import (
 "fmt"
 "github.com/spf13/cobra"
)

// userCmd 父命令
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","","用戶名")
}

4.2 查看命令

➜ ./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   用戶名

4.3 編譯運行

# 編譯
➜ go build -o app .

# 運行
➜ ./app user add -n 張三
添加用戶: 張三
➜ ./app user del -n 張三
刪除用戶: 張三
  1. 標誌 (flags)

cobra的標誌指的就是參數的名稱,但是有本地標誌持久化標誌之分, 白話描述:

5.1 使用示例

修改用戶操作命令 cmd/user.go

1. 修改後的腳本

文件位置:cmd/user.go

package cmd

import (
 "fmt"
 "github.com/spf13/cobra"
)

// userCmd 父命令
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{},"用戶列表")
}

2. 運行測試

# 傳入本地標誌
➜  ./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
  1.  參數限制

6.1 位置參數限制

1. 代碼示例

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)
  },
 }
)

@注意:上面規則限制的是位置參數,並不是標誌,不要混淆。

2. 運行測試

# 傳入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 4

6.2 標誌參數限制

標誌默認是可選的。如果你想在缺少標誌時命令報錯,可使用MarkFlagRequired限制

1. 代碼示例

func init() {
 // 添加子命令到父命令
 userCmd.AddCommand(userAddCmd)
 rootCmd.AddCommand(userCmd)
 // 標誌
 userAddCmd.Flags().StringVar(&name,"name","","用戶名")
 // 標誌必需
 err := userAddCmd.MarkFlagRequired("name")
 if err != nil {
  fmt.Println("--name 不能爲空")
  return
 }
}

2. 運行測試

# 不傳--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: 張三

6.3 自定義位置限制

1. 代碼示例

package cmd

import (
 "errors"
 "fmt"
 "github.com/spf13/cobra"
 "unicode/utf8"
)

// userCmd 父命令
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)
}

2. 運行測試

# 姓名正常時
➜ ./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: 參數數量不對
  1. 集成 viper

7.1 查看目錄結構

├── app
│   └── config
│       ├── app.go # 配置結構體
│       └── app.yaml # 配置文件
├── cmd
│   ├── root.go # 根命令
│   └── server.go # http服務
├── go.mod
├── go.sum
├── local.yaml #
└── main.go

7.2 代碼實現

1. 解析配置: cmd/root.go

package cmd

import (
 "52lu/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 )")
}

// rootCmd represents the base command when called without any subcommands
// 根命令
var rootCmd = &cobra.Command{
 Use:   "",
 Short: "命令行的簡要描述....",
 Long: `學習使用Cobra,開發cli項目,app: 指的是編譯後的文件名。`,
    // 根命令執行方法,如果有要
 //Run: func(cmd *cobra.Command, args []string) {
 //},
}
var (
 cfgFile string // 配置文件
 appConfig *config.AppConfig // 配置對應的結構體
)
// Execute 將所有子命令添加到root命令並適當設置標誌。
// 這由 main.main() 調用。它只需要對 rootCmd 調用一次。
func Execute() {
 cobra.CheckErr(rootCmd.Execute())
}

// initConfig reads in config file and ENV variables if set.
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() // read in environment variables that match
 // 讀取配置文件
 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)
}

2. 使用配置:cmd/server.go

package cmd

import (
 "fmt"
 "github.com/gin-gonic/gin"
 "github.com/spf13/cobra"
 "os"
)
// serverCmd represents the server command
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)
 // 接收參數port
 serverCmd.Flags().StringVar(&port, "port""""端口號")
}

3. 具體配置詳情

app/config/app.go

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/config/app.yaml

app:
  version: v1.0.0
  author: 劉慶輝
  port: 8080
mysql:
  host: 127.0.0.1
  data_base: test
  user: root
  password: root

./local.yaml

app:
  version: v1.0.2
  author: 劉慶輝
  port: 8081
mysql:
  host: 192.168.0.10
  data_base: test
  user: root
  password: root

7.3 編譯運行

# 編譯
➜ 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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/AczejKMuA_-AA_2ETrL4bw