手把手教你開發docker一樣的命令列
- 前言
- 一、安裝
- 二、初始化應用
- 初始化專案
- 建立入口檔案cmd/root.go
- 建立主程式main.go
- 三、如何自定義命令
- 建立hello子命令
- 建立version子命令
- 四、如何設定flag選項
- 全域性選項
- 本地選項
- 設定必填
- 繫結配置
- 五、如何設定arguments
- 使用示例
- 六、如何使用引數
- 獲取flag引數
- 獲取args引數
- 七、如何設定鉤子
- 總結
前言
Cobra是一個強大的用來構建命令列程式的庫,許多流行的Go專案都是用它來構建的,比如Kubernetes、Docker、etcd、Istio、Github CLI等等。
接下來,演示開發一個我們自己的命令列程式chenqionghe,模仿一下docker命令列,預期功能如下
# 檢視幫助
chenqiong -h
# 檢視版本,類似docker version
chenqionghe version
# 檢視hello命令幫助,類似docker ps -h
chenqionghe hello -h
# 使用hello命令,類似docker run --name app --volume /app/data
chenqionghe hello --name light-weight-baby --author gym
Cobra基於三個基本概念
- commands(行為)
- arguments(位置引數)
- flags(命令列選項)
使用基本模式是APPNAME VERB NOUN --ADJECTIVE或APPNAME COMMAND ARG --FLAG,例如
# server是一個command,--port=1313是一個命令列選項
hugo server --port=1313
# clone 是 commands,URL 是 arguments,brae是命令列選項
git clone URL --bare
一、安裝
go get -u github.com/spf13/cobra/cobra
go install github.com/spf13/cobra/cobra
二、初始化應用
初始化專案
這裡我的應用名叫chenqionghe
go mod init chenqionghe
建立入口檔案cmd/root.go
建立資料夾cmd,並建立檔案cmd/root.go,這是用來放所有的命令的基本檔案
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var rootCmd = &cobra.Command{
Use: "chenqionghe",
Short: "getting muscle is not easy",
Long: `let's do it, yeah buddy light weight baby!`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello chenqionghe")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
建立主程式main.go
package main
import "chenqionghe/cmd"
func main() {
cmd.Execute()
}
執行一下main.go可以看到生效了
三、如何自定義命令
建立hello子命令
cobra add hello
會在cmd下生成一個hello.cmd的命令,生成的命令是長下面這樣的,核心是呼叫了AddCommand方法
我們把沒用的資訊幹掉,精簡後如下
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令簡介",
Long: `hello命令詳細介紹`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello called")
},
TraverseChildren: true,
}
func init() {
rootCmd.AddCommand(helloCmd)
}
直接執行看看
go run main.go hello
建立version子命令
同理,我們再建立一個version命令
cobra add version
修改一下Run方法
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("chenqionghe version v0.0.1")
},
執行如下
四、如何設定flag選項
flag選項按作用範圍分為persistent和local兩類
全域性選項
persistent是全域性選項,對應的方法為PersistentFlags,可以分配給命令和命令下的所有子命令,上面的rootCmd和helloCmd都是可以呼叫flag
例如,新增一個-v選項
func init() {
var Verbose bool
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "全域性版本")
}
執行,可以看到生效了
本地選項
local為本地選項,對應方法為Flags,只對指定的Command生效,我們往hello命令的init裡邊新增一個本地flag
func init() {
rootCmd.AddCommand(helloCmd)
//本地flag
var Source string
helloCmd.Flags().StringVarP(&Source, "source", "s", "", "讀取檔案路徑")
}
執行如下
設定必填
我們在init函式新增以下程式碼
rootCmd.Flags().StringVarP(&Name, "name", "n", "", "你的名字")
rootCmd.MarkFlagRequired("name")
執行如下,必須填寫name引數才可以執行
繫結配置
新增一個initConfig方法
func initConfig() {
viper.AddConfigPath("./")
viper.AddConfigPath("./conf")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
}
在init中呼叫
cobra.OnInitialize(initConfig) //這會執行每個子命令之前執行
rootCmd.PersistentFlags().StringVar(&Author, "author", "defaultAuthor", "作者名")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
這將把viper配置和flag繫結,如果使用者不設定-author選項,將從配置中查詢
五、如何設定arguments
cobra預設提供了一些驗證方法
- NoArgs: 如果包含任何位置引數,命令報錯
- ArbitraryArgs: 命令接受任何引數
- OnlyValidArgs: 如果有位置引數不在ValidArgs中,命令報錯
- MinimumArgs(init): 如果引數數目少於N個後,命令列報錯
- MaximumArgs(init): 如果引數數目多餘N個後,命令列報錯
- ExactArgs(init): 如果引數數目不是N個話,命令列報錯
- RangeArgs(min, max): 如果引數數目不在範圍(min, max)中,命令列報錯
使用示例
往Command中新增引數Args,我們規定引數不能少於5個,如下
var rootCmd = &cobra.Command{
Use: "chenqionghe",
Short: "getting muscle is not easy",
Long: `let's do it, yeah buddy light weight baby!`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("hello chenqionghe")
},
Args: cobra.MinimumNArgs(5),
}
執行輸出
六、如何使用引數
我們可以看到核心的方法,其實就是cobra.Command中的Run引數,指定了func(cmd *cobra.Command, args []string)型別的回撥
代表我們可以直接使用cmd和args來編寫我們的程式
獲取flag引數
我們可以直接使用cmd的flag方法獲取傳遞的flag
var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令簡介",
Long: `hello命令詳細介紹`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(cmd.Flag("author").Value)
fmt.Println(cmd.Flag("name").Value)
},
}
執行如下
獲取args引數
var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令簡介",
Long: `hello命令詳細介紹`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(args)
},
TraverseChildren: true,
}
呼叫如下,可以看到已經取出了所有的args引數
七、如何設定鉤子
cobra提供了很多鉤子方法,可按執行順序排列如下
- PersistentPreRun
- PreRun
- Run
- PostRun
- PersistentPostRun
使用示例
var helloCmd = &cobra.Command{
Use: "hello",
Short: "hello命令簡介",
Long: `hello命令詳細介紹`,
//Args: cobra.MinimumNArgs(1),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Run with args: %v\n", args)
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
},
}
執行如下
總結
到這裡,我們就已經學會了如果設定子命令、flag引數、arguments引數以及編寫方法使用這些引數,
還有最後一步,就是編譯出我們的二進位制程式,驗證一下我們之前的需求
- 編譯
go build -o chenqionghe
如下,已經生成二進位制檔案chenqionghe
- 執行命令
./chenqionghe -h # 檢視幫助
./chenqionghe version # 檢視版本,類似docker version
./chenqionghe hello -h # 檢視hello命令幫助,類似docker ps -h
./chenqionghe hello --name light-weight-baby --author gym # 使用hello命令,類似docker run --name app --volume /app/data
可以看到,完美的實現了預期需求,就是這麼簡單,light weight ba