文章首發(fā)于個(gè)人公眾號(hào):「阿拉平平」
最近折騰了下命令行庫(kù) Cobra,和大家分享下蝴猪。本文演示環(huán)境為 CentOS 7.5若厚,Golang 1.11。
文章目錄:
Cobra 介紹
1.1 概念
1.2 安裝
1.3 初始化
1.4 代碼分析Cobra 實(shí)踐
2.1 子命令
2.2 子命令嵌套
2.3 參數(shù)
2.4 標(biāo)志
2.5 讀取配置
2.6 編譯運(yùn)行
1. Cobra 介紹
Cobra 是一個(gè)用來(lái)創(chuàng)建命令行的 golang 庫(kù)蒿辙,同時(shí)也是一個(gè)用于生成應(yīng)用和命令行文件的程序。
1.1 概念
Cobra 結(jié)構(gòu)由三部分組成:命令 (commands)滨巴、參數(shù) (arguments)思灌、標(biāo)志 (flags)」。基本模型如下:
APPNAME VERB NOUN --ADJECTIVE
或者 APPNAME COMMAND ARG --FLAG
如果不是太理解的話泰偿,沒(méi)關(guān)系,我們先看個(gè)例子:
hugo server --port=1313
- hugo:根命令
- server:子命令
- --port:標(biāo)志
再看個(gè)帶有參數(shù)的例子:
git clone URL --bare
- git:根命令
- clone:子命令
- URL:參數(shù)蜈垮,即 clone 作用的對(duì)象
- --bare:標(biāo)志
總結(jié)下:
- commands 代表行為耗跛,是應(yīng)用的中心點(diǎn)
- arguments 代表行為作用的對(duì)象
- flags 是行為的修飾符
相信看了例子后,應(yīng)該有個(gè)直觀的認(rèn)識(shí)了攒发。接下來(lái)我們安裝 Cobra调塌。
1.2 安裝
安裝很簡(jiǎn)單:
go get -u github.com/spf13/cobra/cobra
但是由于網(wǎng)絡(luò)原因,有些包會(huì)下載失敗惠猿,提示 i/o timeout
:
package golang.org/x/sys/unix: unrecognized import path "golang.org/x/sys/unix" (https fetch: Get https://golang.org/x/sys/unix?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
package golang.org/x/text/transform: unrecognized import path "golang.org/x/text/transform" (https fetch: Get https://golang.org/x/text/transform?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
package golang.org/x/text/unicode/norm: unrecognized import path "golang.org/x/text/unicode/norm" (https fetch: Get https://golang.org/x/text/unicode/norm?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
網(wǎng)上解決方法很多羔砾,這里我推薦使用 gopm 來(lái)下載:
# 下載 gopm,之后會(huì)在 $GOPATH/bin 目錄下生成 gopm
go get -u github.com/gpmgo/gopm
# 使用 gopm 來(lái)下載 cobra
gopm get -u -g github.com/spf13/cobra/cobra
下載完成后安裝 cobra 工具,在 $GOPATH/bin
會(huì)生成可執(zhí)行文件:
go install github.com/spf13/cobra/cobra
將生成的 cobra 工具放到 $PATH
目錄下姜凄,可以看到:
[root@localhost ~]# cp -a $GOPATH/bin/cobra /usr/local/bin
[root@localhost ~]# cobra
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.
Usage:
cobra [command]
Available Commands:
add Add a command to a Cobra Application
help Help about any command
init Initialize a Cobra Application
Flags:
-a, --author string author name for copyright attribution (default "YOUR NAME")
--config string config file (default is $HOME/.cobra.yaml)
-h, --help help for cobra
-l, --license string name of license for the project
--viper use Viper for configuration (default true)
Use "cobra [command] --help" for more information about a command.
接下來(lái)我們初始化一個(gè)項(xiàng)目政溃。
1.3 初始化
通過(guò) cobra init
初始化 demo 項(xiàng)目:
[root@localhost ~]# cd $GOPATH/src
[root@localhost src]# cobra init demo --pkg-name=demo
Your Cobra applicaton is ready at
/root/go/src/demo
當(dāng)前項(xiàng)目結(jié)構(gòu)為:
demo
├── cmd
│ └── root.go
├── LICENSE
└── main.go
可以看到初始化后的項(xiàng)目非常簡(jiǎn)單,主要是 main.go
和 root.go
文件檀葛。在編寫代碼之前玩祟,我們先分析下目前代碼的邏輯腹缩。
1.4 代碼分析
先查看下入口文件 main.go
屿聋。代碼邏輯很簡(jiǎn)單,就是調(diào)用 cmd 包里 Execute()
函數(shù):
package main
import "demo/cmd"
func main() {
cmd.Execute()
}
再看下 root.go
中 rootCmd 的字段:
...
var rootCmd = &cobra.Command{
Use: "demo",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
...
簡(jiǎn)單說(shuō)明下:
- Use:命令名
- Short & Long:幫助信息的文字內(nèi)容
- Run:運(yùn)行命令的邏輯
Command 結(jié)構(gòu)體中的字段當(dāng)然遠(yuǎn)不止這些藏鹊,受限于篇幅润讥,這里無(wú)法全部介紹。有興趣的童鞋可以查閱下官方文檔盘寡。
運(yùn)行測(cè)試:
[root@localhost demo]# go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.
subcommand is required
exit status 1
如果運(yùn)行的結(jié)果和我的一致楚殿,那我們就可以進(jìn)入到實(shí)踐環(huán)節(jié)了。
2. Cobra 實(shí)踐
鋪墊了這么久竿痰,終于可以開(kāi)始實(shí)踐了脆粥。實(shí)踐環(huán)節(jié)中,我會(huì) 提一些需求影涉,然后我們一起實(shí)現(xiàn)一個(gè)簡(jiǎn)單的命令行工具变隔。
2.1 子命令
之前運(yùn)行會(huì)提示 subcommand is required
,是因?yàn)楦顭o(wú)法直接運(yùn)行蟹倾。那我們就添加個(gè)子命令試試匣缘。
通過(guò) cobra add
添加子命令 create
:
[root@localhost demo]# cobra add create
create created at /root/go/src/demo
當(dāng)前項(xiàng)目結(jié)構(gòu)為:
demo
├── cmd
│ ├── create.go
│ └── root.go
├── LICENSE
└── main.go
查看下 create.go
,init()
說(shuō)明了命令的層級(jí)關(guān)系:
...
func init() {
rootCmd.AddCommand(createCmd)
}
運(yùn)行測(cè)試:
# 輸入正確
[root@localhost demo]# go run main.go create
create called
# 未知命令
[root@localhost demo]# go run main.go crea
Error: unknown command "crea" for "demo"
Did you mean this?
create
Run 'demo --help' for usage.
unknown command "crea" for "demo"
Did you mean this?
create
2.2 子命令嵌套
對(duì)于功能相對(duì)復(fù)雜的 CLI鲜棠,通常會(huì)通過(guò)多級(jí)子命令肌厨,即:子命令嵌套的方式進(jìn)行描述,那么該如何實(shí)現(xiàn)呢豁陆?
demo create rule
首先添加子命令 rule
:
[root@localhost demo]# cobra add rule
rule created at /root/go/src/demo
當(dāng)前目錄結(jié)構(gòu)如下:
demo
├── cmd
│ ├── create.go
│ ├── root.go
│ └── rule.go
├── LICENSE
└── main.go
目前create
和 rule
是同級(jí)的柑爸,所以需要修改 rule.go
的 init()
來(lái)改變子命令間的層級(jí)關(guān)系:
...
func init() {
// 修改子命令的層級(jí)關(guān)系
//rootCmd.AddCommand(ruleCmd)
createCmd.AddCommand(ruleCmd)
}
雖然調(diào)整了命令的層級(jí)關(guān)系,但是目前運(yùn)行 demo create
會(huì)打印 create called
盒音,我希望運(yùn)行時(shí)可以打印幫助提示表鳍。所以我們繼續(xù)完善下代碼,修改 create.go
:
...
var createCmd = &cobra.Command{
Use: "create",
Short: "create",
Long: "Create Command.",
Run: func(cmd *cobra.Command, args []string) {
// 如果 create 命令后沒(méi)有參數(shù)里逆,則提示幫助信息
if len(args) == 0 {
cmd.Help()
return
}
},
}
...
運(yùn)行測(cè)試:
- 直接運(yùn)行
create
进胯,打印幫助提示:
[root@localhost demo]# go run main.go create
Create Command.
Usage:
demo create [flags]
demo create [command]
Available Commands:
rule A brief description of your command
Flags:
-h, --help help for create
Global Flags:
--config string config file (default is $HOME/.demo.yaml)
Use "demo create [command] --help" for more information about a command.
- 運(yùn)行
create rule
,輸出rule called
:
[root@localhost demo]# go run main.go create rule
rule called
2.3 參數(shù)
先說(shuō)說(shuō)參數(shù)≡海現(xiàn)在有個(gè)需求:給 CLI 加個(gè)位置參數(shù)胁镐,要求參數(shù)有且僅有一個(gè)。這個(gè)需求我們要如何實(shí)現(xiàn)呢?
demo create rule foo
實(shí)現(xiàn)前先說(shuō)下盯漂,Command 結(jié)構(gòu)體中有個(gè) Args 的字段颇玷,接受類型為 type PositionalArgs func(cmd *Command, args []string) error
內(nèi)置的驗(yàn)證方法如下:
- NoArgs:如果有任何參數(shù),命令行將會(huì)報(bào)錯(cuò)
- ArbitraryArgs: 命令行將會(huì)接收任何參數(shù)
- OnlyValidArgs: 如果有如何參數(shù)不屬于 Command 的 ValidArgs 字段就缆,命令行將會(huì)報(bào)錯(cuò)
- MinimumNArgs(int): 如果參數(shù)個(gè)數(shù)少于 N 個(gè)帖渠,命令行將會(huì)報(bào)錯(cuò)
- MaximumNArgs(int): 如果參數(shù)個(gè)數(shù)多于 N 個(gè),命令行將會(huì)報(bào)錯(cuò)
- ExactArgs(int): 如果參數(shù)個(gè)數(shù)不等于 N 個(gè)竭宰,命令行將會(huì)報(bào)錯(cuò)
- RangeArgs(min, max): 如果參數(shù)個(gè)數(shù)不在 min 和 max 之間, 命令行將會(huì)報(bào)錯(cuò)
由于需求里要求參數(shù)有且僅有一個(gè)空郊,想想應(yīng)該用哪個(gè)內(nèi)置驗(yàn)證方法呢?相信你已經(jīng)找到了 ExactArgs(int)切揭。
改寫下 rule.go
:
...
var ruleCmd = &cobra.Command{
Use: "rule",
Short: "rule",
Long: "Rule Command.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Create rule %s success.\n", args[0])
},
}
...
運(yùn)行測(cè)試:
- 不輸入?yún)?shù):
[root@localhost demo]# go run main.go create rule
Error: accepts 1 arg(s), received 0
- 輸入 1 個(gè)參數(shù):
[root@localhost demo]# go run main.go create rule foo
Create rule foo success.
- 輸入 2 個(gè)參數(shù):
[root@localhost demo]# go run main.go create rule
Error: accepts 1 arg(s), received 2
從測(cè)試的情況看狞甚,運(yùn)行的結(jié)果符合我們的預(yù)期。如果需要對(duì)參數(shù)進(jìn)行復(fù)雜的驗(yàn)證廓旬,還可以自定義 Args哼审,這里就不多做贅述了。
2.4 標(biāo)志
再說(shuō)說(shuō)標(biāo)志≡斜現(xiàn)在要求 CLI 不接受參數(shù)涩盾,而是通過(guò)標(biāo)志 --name
對(duì) rule
進(jìn)行描述。這個(gè)又該如何實(shí)現(xiàn)励背?
demo create rule --name foo
Cobra 中有兩種標(biāo)志:持久標(biāo)志 ( Persistent Flags ) 和 本地標(biāo)志 ( Local Flags ) 春霍。
持久標(biāo)志:指所有的 commands 都可以使用該標(biāo)志。比如:--verbose 椅野,--namespace
本地標(biāo)志:指特定的 commands 才可以使用該標(biāo)志终畅。
這個(gè)標(biāo)志的作用是修飾和描述 rule
的名字,所以選用本地標(biāo)志竟闪。修改 rule.go
:
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// 添加變量 name
var name string
var ruleCmd = &cobra.Command{
Use: "rule",
Short: "rule",
Long: "Rule Command.",
Run: func(cmd *cobra.Command, args []string) {
// 如果沒(méi)有輸入 name
if len(name) == 0 {
cmd.Help()
return
}
fmt.Printf("Create rule %s success.\n", name)
},
}
func init() {
createCmd.AddCommand(ruleCmd)
// 添加本地標(biāo)志
ruleCmd.Flags().StringVarP(&name, "name", "n", "", "rule name")
}
說(shuō)明:StringVarP
用來(lái)接收類型為字符串變量的標(biāo)志离福。相較StringVar
, StringVarP
支持標(biāo)志短寫炼蛤。以我們的 CLI 為例:在指定標(biāo)志時(shí)可以用 --name
妖爷,也可以使用短寫 -n
。
運(yùn)行測(cè)試:
# 這幾種寫法都可以執(zhí)行
[root@localhost demo]# go run main.go create rule -n foo
Create rule foo success.
[root@localhost demo]# go run main.go create rule --name foo
Create rule foo success.
[root@localhost demo]# go run main.go create -n foo rule
Create rule foo success.
2.5 讀取配置
最后說(shuō)說(shuō)配置理朋。需求:要求 --name
標(biāo)志存在默認(rèn)值絮识,且該值是可配置的。
如果只需要標(biāo)志提供默認(rèn)值嗽上,我們只需要修改 StringVarP
的 value
參數(shù)就可以實(shí)現(xiàn)次舌。但是這個(gè)需求關(guān)鍵在于標(biāo)志是可配置的,所以需要借助配置文件兽愤。
很多情況下彼念,CLI 是需要讀取配置信息的挪圾,比如 kubectl 的~/.kube/config
。在幫助提示里可以看到默認(rèn)的配置文件為 $HOME/.demo.yaml
:
Global Flags:
--config string config file (default is $HOME/.demo.yaml)
?配置庫(kù)我們可以使用 Viper逐沙。Viper 是 Cobra 集成的配置文件讀取庫(kù)哲思,支持 YAML
,JSON
吩案, TOML
棚赔, HCL
等格式的配置。
添加配置文件 $HOME/.demo.yaml
徘郭,增加 name 字段:
[root@localhost ~]# vim $HOME/.demo.yaml
name: foo
修改 rule.go
:
package cmd
import (
"fmt"
// 導(dǎo)入 viper 包
"github.com/spf13/viper"
"github.com/spf13/cobra"
)
var name string
var ruleCmd = &cobra.Command{
Use: "rule",
Short: "rule",
Long: "Rule Command.",
Run: func(cmd *cobra.Command, args []string) {
// 不輸入 --name 從配置文件中讀取 name
if len(name) == 0 {
name = viper.GetString("name")
// 配置文件中未讀取到 name靠益,打印幫助提示
if len(name) == 0 {
cmd.Help()
return
}
}
fmt.Printf("Create rule %s success.\n", name)
},
}
func init() {
createCmd.AddCommand(ruleCmd)
ruleCmd.Flags().StringVarP(&name, "name", "n", "", "rule name")
}
運(yùn)行測(cè)試:
[root@localhost demo]# go run main.go create rule
Using config file: /root/.demo.yaml
Create rule foo success.
如果 CLI 沒(méi)有用到配置文件,可以在初始化項(xiàng)目的時(shí)候關(guān)閉 Viper 的選項(xiàng)以減少編譯后文件的體積崎岂,如下:
cobra init demo --pkg-name=demo --viper=false
2.6 編譯運(yùn)行
?編譯生成命令行工具:
[root@localhost demo]# go build -o demo
運(yùn)行測(cè)試:
[root@localhost demo]# ./demo create rule
Using config file: /root/.demo.yaml
Create rule foo success.
參考文檔:
- Github - https://github.com/spf13/cobra
- Cobra 的一些筆記 - https://zhangguanzhang.github.io/2019/06/02/cobra/
- Golang之使用Cobra - https://o-my-chenjian.com/2017/09/20/Using-Cobra-With-Golang/
- golang命令行庫(kù)Cobra的使用 - http://www.reibang.com/p/7abe7cff5384