Go 常用包: 構建現代 CLI 應用庫 Cobra
- 介紹
cobra
是一個用來構建現代 CLI 工具的庫。相比flag
標準庫,它提供更多方便的特性和功能。Cobra
由 Go
項目成員和 hugo
作者 spf13 創建,已經被許多流行的 Go
項目採用,比如 GitHub CLI 和 Docker CLI。
源碼地址: https://github.com/spf13/cobra,截止到現在
Star 23.8K
1.1 特性預覽
-
使用
cobra add cmdname
可快速的創建子命令cli
-
全局、局部和級聯的標誌
-
自動生成
commands
和flags
的幫助信息 -
自動識別
-h
、--help
等幫助標識 -
支持自定義幫助信息,用法等的靈活性。
-
可與 viper 緊密集成
1.2 相關概念
Cobra
結構由三部分組成:命令 (commands
)、參數 (arguments
)、標誌 (flags
)。最好的應用程序在使用時讀起來像句子,要遵循的模式:
# 沒有子命令
`app cmd --param=?`:
# 有子命令
`app cmd subCmd --param=?`
app
: 代表編譯後的文件名,cmd
: 代表命令subCmd
: 代表子命令--param
: 代表請求參數。
- 安裝
go get -u github.com/spf13/cobra/cobra
- 快速使用
快速創建一個
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
- 嵌套子命令
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 張三
刪除用戶: 張三
- 標誌 (
flags
)
cobra
的標誌指的就是參數的名稱,但是有本地標誌和持久化標誌之分, 白話描述:
-
本地標誌 (
Flags
): 當前命令接收, 當前命令使用。 -
持久標誌 (
PersistentFlags
): 當前命令接收,當前命令行和其所有子命令都可使用。
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
- 參數限制
6.1 位置參數限制
-
NoArgs
: 如果有任何位置參數,該命令將報告錯誤。 -
MinimumNArgs(int)
: 至少傳 N 個位置參數,否則報錯。 -
ArbitraryArgs
: 接受任意個位置參數。 -
MaximumNArgs(int)
: 最多傳 N 個位置參數,否則報錯。 -
ExactArgs(int)
: 傳入位置參數個數等於 N,否則報錯。 -
RangeArgs(min, max)
: 傳入位置參數個數min<= N <= max
,否則報錯
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: 參數數量不對
- 集成 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