來自jetbrains Go 語言現狀調查報告 顯示:在go開發者中使用go開發實用小程式的比例為31%僅次於web,go得益於跨平臺、無依賴的特性,用來編寫命令列或系統管理這類小程式非常不錯。
本文主要介紹Steve Francia(spf13)大神寫的用於快速構建命令列程式的golang包cobra,基於cobra寫命令列的著名專案一隻手數不過來:Docker CLI、Helm、istio、etcd、Git、Github CLI ...
下面進入正題
cobra能幫我們做啥?
cobra包提供以下功能:
- 輕鬆建立基於子命令的 CLI:如
app server
、app fetch
等。 - 自動新增
-h
,--help
等幫助性Flag - 自動生成命令和Flag的幫助資訊
- 建立完全符合 POSIX 的Flag(標誌)(包括長、短版本)
- 支援巢狀子命令
- 支援全域性、本地和級聯Flag
- 智慧建議(
app srver
... did you meanapp server
?) - 為應用程式自動生成 shell 自動完成功能(bash、zsh、fish、powershell)
- 為應用程式自動生成man page
- 命令別名,可以在不破壞原有名稱的情況下進行更改
- 支援靈活自定義help、usege等。
- 無縫整合viper構建12-factor應用
cobra遵循commands
, arguments
& flags
結構。
舉例來說
#appname command arguments
docker pull alpine:latest
#appname command flag
docker ps -a
#appname command flag argument
git commit -m "msg"
開發者可根據情況進行自組織。
cobra基礎使用
安裝cobra包和二進位制工具cobra-cli,cobra-cli可以幫助我們快速建立出一個cobra基礎程式碼結構。
go get -u github.com/spf13/cobra@latest
go install github.com/spf13/cobra-cli@latest
啟用GO111MODULE=on
,我們初始化一個xpower
# go mod init xpower
go: creating new go.mod: module xpower
使用cobra-cli初始化基礎程式碼結構
# cobra-cli init
Your Cobra application is ready at /root/demo/xpower
#檢視目錄結構
# tree xpower
xpower
├── cmd
│ └── root.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
1 directory, 5 files
執行demo可以看到cobra包本身的一些提示資訊。
檢視main.go
,cobra-cli為我們建立了一個cmd的包並且呼叫了包裡面的Execute()
函式
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
*/
package main
import "xpower/cmd"
func main() {
cmd.Execute()
}
從上面的目錄結構中可以看到cmd包目前只有一個root.go
,我們可以在這裡操作根命令相關的內容。
大多數時候CLI可能會包含多個子命令比如git clone
、git add
,cobra-cli可通過add 新增子命令。
現在我們新增wget和ping子命令,即接下來我們將通過xpower來重寫wget和ping的部分功能。
cobra-cli add wget
cobra-cli add ping
現在的目錄結構如下:
# tree xpower
xpower
├── cmd
│ ├── ping.go
│ ├── root.go
│ └── wget.go
├── go.mod
├── go.sum
├── LICENSE
└── main.go
ping
和wget
已經被整合到root.go中
wget.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// wgetCmd represents the wget command
var wgetCmd = &cobra.Command{
Use: "wget",
Example: "xpower wget iqsing.github.io/download.tar -o /tmp",
Short: "wget is a download cli.",
Long: `use wget to download everything you want from net.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("wget called")
},
}
func init() {
rootCmd.AddCommand(wgetCmd)
// Here you will define your flags and configuration settings.
}
在wget.go 中定義了一個wgetCmd結構體指標,可通過檢視Command結構體原型新增或移除成員變數。這裡我們新增了一個Example
用於指示示例,Short和Long為命令簡介,Run為wget命令的真正實現。
我們知道在go中包的init()函式會在import時執行,通過AddCommand(wgetCmd)
將wegetCmd新增到結構體Command
成員變數commands中,包括後面我們編寫的Flag也是如此。
接下來我們在結構體中新增Args用於驗證(限制)引數數量,在init()函式中新增Flag -o
用於儲存下載的檔案地址,並通過MarkFlagRequired
約束flag的引數必須輸入,最後在Run中呼叫Download即可。
package cmd
import (
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/spf13/cobra"
)
var (
output string
)
// wgetCmd represents the wget command
var wgetCmd = &cobra.Command{
Use: "wget",
Example: "xpower wget iqsing.github.io/download.tar.gz -o /tmp/download.tar.gz",
Args: cobra.ExactArgs(1),
Short: "wget is a download cli.",
Long: `use wget to download everything you want from net.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("---wget running---")
Download(args[0], output)
},
}
func init() {
rootCmd.AddCommand(wgetCmd)
// Here you will define your flags and configuration settings.
wgetCmd.Flags().StringVarP(&output, "output", "o", "", "output file")
wgetCmd.MarkFlagRequired("output")
}
func Download(url string, path string) {
out, err := os.Create(path)
check(err)
defer out.Close()
res, err := http.Get(url)
check(err)
defer res.Body.Close()
_, err = io.Copy(out, res.Body)
check(err)
fmt.Println("save as" + path)
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
args
Args: cobra.ExactArgs(1)
cobra內建的引數驗證也是比較多,NoArgs、OnlyValidArgs、MinimumNArgs、MaximumNArgs等等可翻閱原始碼args.go,可以滿足基本使用,如果有自己的特殊要求可以通過解析arg來實現。
flags
wgetCmd.Flags().StringVarP(&output, "output", "o", "", "output file(required)")
flag包含區域性和全域性兩種,全域性flag在父命令定義後子命令也會生效,而區域性flag則在哪定義就在哪生效。
如上面的區域性flag,我們在wgetCmd中定義的flag只有wget這個子命令能用。
全域性flag
rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
StringVarp
、BoolVarP
用於flag資料型別限制。
簡單的應用從命令列直接寫入引數是很常見的,但是如果比較複雜的命令列應用引數需要非常多,再這樣操作不太合理,cobra作者還寫了另一個在go中很流行的包viper用於解析配置檔案,比如kubectl 的yml,以及各種json
前面也說過可以無縫銜接,只需Bind一下即可。
var author string
func init() {
rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution")
viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
}
flag還可以做依賴,比如下面username和password必須同時接收到引數。
rootCmd.Flags().StringVarP(&u, "username", "u", "", "Username (required if password is set)")
rootCmd.Flags().StringVarP(&pw, "password", "p", "", "Password (required if username is set)")
rootCmd.MarkFlagsRequiredTogether("username", "password")
新增子命令可參考包go-ping/ping,這裡不再贅述。
我們來看編譯後使用如何?
通過-h
檢視幫助:
引數個數錯誤:
需要flag-o
:
正確使用:
xpower 子命令ping:
xpower 子命令wget:
以上我們通過go中cobra包實現xpower命令,包含重寫了簡單功能的ping和wget兩子命令,甚至我們還可以以此來實現自己的跨平臺、無依賴的工具集。本文涉及程式碼已提交至倉庫code/xpower
cobra包含很多開箱即用的功能,經過大量專案驗證和完善,已滿足大部分命令列應用構建需求。本文只介紹了一部分內容,更多內容可檢視倉庫spf13/cobra
通過部落格閱讀:iqsing.github.io