教你用Cobra開發類似docker的命令行
- 2020 年 4 月 8 日
- 筆記
前言
Cobra是一個強大的用來構建命令行程序的庫,許多流行的Go項目都是用它來構建的,比如Kubernetes、Docker、etcd、Istio、Github CLI等等。
老實說,docker和Kubernetes都在用,不會這個說不過去了吧,畢竟我們是要能改源代碼的程序猿!
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
接下來,演示開發一個我們自己的命令行程序chenqionghe,預期功能如下
chenqiong -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
一、安裝
go get -u github.com/spf13/cobra/cobra go install github.com/spf13/cobra/cobra
二、初始化應用
gomod初始化
這裡我的應用名叫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可以看到生效了
三、生成Command
創建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為本地選項,對應方法為Flag,只在針對指定的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: %vn", args) }, PreRun: func(cmd *cobra.Command, args []string) { fmt.Printf("Inside rootCmd PreRun with args: %vn", args) }, Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Run with args: %vn", args) }, PostRun: func(cmd *cobra.Command, args []string) { fmt.Printf("Inside rootCmd PostRun with args: %vn", args) }, PersistentPostRun: func(cmd *cobra.Command, args []string) { fmt.Printf("Inside rootCmd PersistentPostRun with args: %vn", 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 baby!