Cobra 是一個 Golang 包,它提供了簡單的介面來建立命令列程式。同時,Cobra 也是一個應用程式,用來生成應用框架,從而開發以 Cobra 為基礎的應用。本文的演示環境為 ubuntu 18.04(下圖來自網際網路)。
主要功能
cobra 的主要功能如下,可以說每一項都很實用:
- 簡易的子命令列模式,如 app server, app fetch 等等
- 完全相容 posix 命令列模式
- 巢狀子命令 subcommand
- 支援全域性,區域性,串聯 flags
- 使用 cobra 很容易的生成應用程式和命令,使用 cobra create appname 和 cobra add cmdname
- 如果命令輸入錯誤,將提供智慧建議,如 app srver,將提示 srver 沒有,是不是 app server
- 自動生成 commands 和 flags 的幫助資訊
- 自動生成詳細的 help 資訊,如 app help
- 自動識別幫助 flag -h,--help
- 自動生成應用程式在 bash 下命令自動完成功能
- 自動生成應用程式的 man 手冊
- 命令列別名
- 自定義 help 和 usage 資訊
- 可選的與 viper apps 的緊密整合
cobra 中的主要概念
cobra 中有個重要的概念,分別是 commands、arguments 和 flags。其中 commands 代表行為,arguments 就是命令列引數(或者稱為位置引數),flags 代表對行為的改變(也就是我們常說的命令列選項)。執行命令列程式時的一般格式為:
APPNAME COMMAND ARG --FLAG
比如下面的例子:
# server是 commands,port 是 flag hugo server --port=1313 # clone 是 commands,URL 是 arguments,brae 是 flag git clone URL --bare
如果是一個簡單的程式(功能單一的程式),使用 commands 的方式可能會很囉嗦,但是像 git、docker 等應用,把這些本就很複雜的功能劃分為子命令的形式,會方便使用(對程式的設計者來說又何嘗不是如此)。
建立 cobra 應用
在建立 cobra 應用前需要先安裝 cobra 包:
$ go get -u github.com/spf13/cobra/cobra
然後就可以用 cobra 程式生成應用程式框架了:
$ cobra init appname
如果不想讓應用程式在預設的目錄下,就要指定應用程式所在的絕對目錄,比如 ~/go/src/godemos/cobrademo,生成的檔案如下:
此時的程式並沒有什麼功能,執行它只會輸出一些預設的提示資訊:
使用 cobra 程式生成命令程式碼
除了生成應用程式框架,還可以通過 cobra add 命令生成子命令的程式碼檔案,比如下面的命令會新增兩個子命令 image 和 container 相關的程式碼檔案:
$ cd ~/go/src/godemos/cobrademo
$ cobra add image
$ cobra add container
這兩條命令分別生成了 cobrademo 程式中 image 和 container 子命令的程式碼,當然了,具體的功能還得靠我們自己實現。
為命令新增具體的功能
到目前為止,我們一共為 cobrademo 程式新增了三個 Command,分別是 rootCmd(cobra init 命令預設生成)、imageCmd 和 containerCmd。
開啟檔案 root.go ,找到變數 rootCmd 的初始化過程併為之設定 Run 方法:
Run: func(cmd *cobra.Command, args []string) { fmt.Println("cobra demo program") },
重新編譯 cobrademo 程式並不帶引數執行,這次就不再輸出幫助資訊了,而是執行了 rootCmd 的 Run 方法:
再建立一個 version Command 用來輸出當前的軟體版本。先在 cmd 目錄下新增 version.go 檔案,編輯檔案的內容如下:
package cmd import ( "fmt" "github.com/spf13/cobra" ) func init() { rootCmd.AddCommand(versionCmd) } var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number of cobrademo", Long: `All software has versions. This is cobrademo's`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("cobrademo version is v1.0") }, }
為 Command 新增選項(flags)
選項(flags)用來控制 Command 的具體行為。根據選項的作用範圍,可以把選項分為兩類:
- persistent
- local
對於 persistent 型別的選項,既可以設定給該 Command,又可以設定給該 Command 的子 Command。對於一些全域性性的選項,比較適合設定為 persistent 型別,比如控制輸出的 verbose 選項:
var Verbose bool rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
local 型別的選項只能設定給指定的 Command,比如下面定義的 source 選項:
var Source string rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
該選項不能指定給 rootCmd 之外的其它 Command。
預設情況下的選項都是可選的,但一些用例要求使用者必須設定某些選項,這種情況 cobra 也是支援的,通過 Command 的 MarkFlagRequired 方法標記該選項即可:
var Name string rootCmd.Flags().StringVarP(&Name, "name", "n", "", "user name (required)") rootCmd.MarkFlagRequired("name")
命令列引數(arguments)
首先我們來搞清楚命令列引數(arguments)與命令列選項的區別(flags/options)。以常見的 ls 命令來說,其命令列的格式為:
ls [OPTION]... [FILE]…
其中的 OPTION 對應本文中介紹的 flags,以 - 或 -- 開頭;而 FILE 則被稱為引數(arguments)或位置引數。一般的規則是引數在所有選項的後面,上面的 … 表示可以指定多個選項和多個引數。
cobra 預設提供了一些驗證方法:
- NoArgs - 如果存在任何位置引數,該命令將報錯
- ArbitraryArgs - 該命令會接受任何位置引數
- OnlyValidArgs - 如果有任何位置引數不在命令的 ValidArgs 欄位中,該命令將報錯
- MinimumNArgs(int) - 至少要有 N 個位置引數,否則報錯
- MaximumNArgs(int) - 如果位置引數超過 N 個將報錯
- ExactArgs(int) - 必須有 N 個位置引數,否則報錯
- ExactValidArgs(int) 必須有 N 個位置引數,且都在命令的 ValidArgs 欄位中,否則報錯
- RangeArgs(min, max) - 如果位置引數的個數不在區間 min 和 max 之中,報錯
比如要讓 Command cmdTimes 至少有一個位置引數,可以這樣初始化它:
var cmdTimes = &cobra.Command{ Use: … Short: … Long: … Args: cobra.MinimumNArgs(1), Run: … }
一個完整的 demo
我們在前面建立的程式碼的基礎上,為 image 命令新增行為(列印資訊到控制檯),併為它新增一個子命令 cmdTimes,下面是更新後的 image.go 檔案的內容(本文 demo 的完整程式碼請參考這裡):
package cmd import ( "fmt" "github.com/spf13/cobra" "strings" ) // imageCmd represents the image command var imageCmd = &cobra.Command{ Use: "image", Short: "Print images information", Long: "Print all images information", Run: func(cmd *cobra.Command, args []string) { fmt.Println("image one is ubuntu 16.04") fmt.Println("image two is ubuntu 18.04") fmt.Println("image args are : " + strings.Join(args, " ")) }, } var echoTimes int var cmdTimes = &cobra.Command{ Use: "times [string to echo]", Short: "Echo anything to the screen more times", Long: `echo things multiple times back to the user by providing a count and a string.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { for i := 0; i < echoTimes; i++ { fmt.Println("Echo: " + strings.Join(args, " ")) } }, } func init() { rootCmd.AddCommand(imageCmd) cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") imageCmd.AddCommand(cmdTimes) }
編譯後執行命令:
$ ./cobrademo image hello $ ./cobrademo image times -t=3 world
因為我們為 cmdTimes 命令設定了 Args: cobra.MinimumNArgs(1),所以必須為 times 子命令傳入一個引數,不然 times 子命令會報錯:
幫助資訊(help command)
cobra 會自動新增 --help(-h)選項,所以我們可以不必新增該選項而直接使用:
cobra 同時還自動新增了 help 子命,預設效果和使用 --help 選項相同。如果為 help 命令傳遞其它命令作為引數,則會顯示對應命令的幫助資訊,下面的命令輸出 image 子命令的幫助資訊:
$ cobrademo help image
當然也可以通過這種方式檢視子命令的子命令的幫助文件:
$ cobrademo help image times
除了 cobra 預設的幫助命令,我們還可以通過下面的方式進行自定義:
cmd.SetHelpCommand(cmd *Command) cmd.SetHelpFunc(f func(*Command, []string)) cmd.SetHelpTemplate(s string)
提示資訊(usage message)
提示資訊和幫助資訊很相似,只不過它是在你輸入了非法的引數、選項或命令時才出現的:
和幫助資訊一樣,我們也可以通過下面的方式自定義提示資訊:
cmd.SetUsageFunc(f func(*Command) error) cmd.SetUsageTemplate(s string)
在 Commnad 執行前後執行額外的操作
Command 執行的操作是通過 Command.Run 方法實現的,為了支援我們在 Run 方法執行的前後執行一些其它的操作,Command 還提供了額外的幾個方法,它們的執行順序如下:
1. PersistentPreRun
2. PreRun
3. Run
4. PostRun
5. PersistentPostRun
修改 rootCmd 的初始化程式碼如下:
var rootCmd = &cobra.Command{ Use: "cobrademo", Short: "sparkdev's cobra demo", Long: "the demo show how to use cobra package", 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("cobra demo program, 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) }, }
重新編譯 cobrademo 程式並執行,輸出結果中可以看到這些方法的執行順序:
其中的 PersistentPreRun 方法和 PersistentPostRun 方法會伴隨任何子命令的執行:
對未知命令的提示
如果我們輸入了不正確的命令或者是選項,cobra 還會只能的給出提示:
上圖的命令我們只輸入了子命令 image 的前兩個字母,但是 cobra 已經可以給出很詳細的提示了。對於這樣的提示我們也是可以自定義的,或者如果覺著沒用就直接關閉掉。
本文 demo 的完整程式碼請參考這裡。
總結
cobra 是一個非常實用(流行)的包,很多優秀的開源應用都在使用它,包括 Docker 和 Kubernetes 等等。當我們熟悉了 cobra 包的基本用法後,再去看 Docker 等應用的命令列工具的格式,是不是就很容易理解了!
參考:
spf13/cobra
Golang之使用Cobra
MAKE YOUR OWN CLI WITH GOLANG AND COBRA
Cobra簡介
golang命令列庫cobra的使用