在上一篇文章中,咱們介紹了flag
庫。flag
庫是用於解析命令行選項的。可是flag
有幾個缺點:git
Type
或TypeVar
函數;bool/int/uint/string
和time.Duration
;爲了解決這些問題,出現了很多第三方解析命令行選項的庫,今天的主角go-flags
就是其中一個。第一次看到go-flags
庫是在閱讀pgweb源碼的時候。github
go-flags
提供了比標準庫flag
更多的選項。它利用結構標籤(struct tag)和反射提供了一個方便、簡潔的接口。它除了基本的功能,還提供了豐富的特性:golang
-aux
;上面只是粗略介紹了go-flags
的特性,下面咱們依次來介紹。web
學習從使用開始!咱們先來看看go-flags
的基本使用。segmentfault
因爲是第三方庫,使用前須要安裝,執行下面的命令安裝:微信
$ go get github.com/jessevdk/go-flags
代碼中使用import
導入該庫:函數
import "github.com/jessevdk/go-flags"
完整示例代碼以下:學習
package main import ( "fmt" "github.com/jessevdk/go-flags" ) type Option struct { Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug message"` } func main() { var opt Option flags.Parse(&opt) fmt.Println(opt.Verbose) }
使用go-flags
的通常步驟:ui
short
和long
設置短、長選項名字,description
設置幫助信息。命令行傳參時,短選項前加-
,長選項前加--
;go-flags
的解析方法解析。編譯、運行代碼(個人環境是 Win10 + Git Bash):this
$ go build -o main.exe main.go
短選項:
$ ./main.exe -v [true]
長選項:
$ ./main.exe --verbose [true]
因爲Verbose
字段是切片類型,每次遇到-v
或--verbose
都會追加一個true
到切片中。
多個短選項:
$ ./main.exe -v -v [true true]
多個長選項:
$ ./main.exe --verbose --verbose [true true]
短選項 + 長選項:
$ ./main.exe -v --verbose -v [true true true]
短選項合寫:
$ ./main.exe -vvv [true true true]
go-flags
相比標準庫flag
支持更豐富的數據類型:
int/int8/int16/int32/int64
,無符號整數uint/uint8/uint16/uint32/uint64
,浮點數float32/float64
,布爾類型bool
和字符串string
)和它們的切片;string
,值爲基礎類型的 map;若是字段是基本類型的切片,基本解析流程與對應的基本類型是同樣的。切片類型選項的不一樣之處在於,遇到相同的選項時,值會被追加到切片中。而非切片類型的選項,後出現的值會覆蓋先出現的值。
下面來看一個示例:
package main import ( "fmt" "github.com/jessevdk/go-flags" ) type Option struct { IntFlag int `short:"i" long:"int" description:"int flag value"` IntSlice []int `long:"intslice" description:"int slice flag value"` BoolFlag bool `long:"bool" description:"bool flag value"` BoolSlice []bool `long:"boolslice" description:"bool slice flag value"` FloatFlag float64 `long:"float", description:"float64 flag value"` FloatSlice []float64 `long:"floatslice" description:"float64 slice flag value"` StringFlag string `short:"s" long:"string" description:"string flag value"` StringSlice []string `long:"strslice" description:"string slice flag value"` PtrStringSlice []*string `long:"pstrslice" description:"slice of pointer of string flag value"` Call func(string) `long:"call" description:"callback"` IntMap map[string]int `long:"intmap" description:"A map from string to int"` } func main() { var opt Option opt.Call = func (value string) { fmt.Println("in callback: ", value) } err := flags.Parse(&opt, os.Args[1:]) if err != nil { fmt.Println("Parse error:", err) return } fmt.Printf("int flag: %v\n", opt.IntFlag) fmt.Printf("int slice flag: %v\n", opt.IntSlice) fmt.Printf("bool flag: %v\n", opt.BoolFlag) fmt.Printf("bool slice flag: %v\n", opt.BoolSlice) fmt.Printf("float flag: %v\n", opt.FloatFlag) fmt.Printf("float slice flag: %v\n", opt.FloatSlice) fmt.Printf("string flag: %v\n", opt.StringFlag) fmt.Printf("string slice flag: %v\n", opt.StringSlice) fmt.Println("slice of pointer of string flag: ") for i := 0; i < len(opt.PtrStringSlice); i++ { fmt.Printf("\t%d: %v\n", i, *opt.PtrStringSlice[i]) } fmt.Printf("int map: %v\n", opt.IntMap) }
基本類型和其切片比較簡單,就不過多介紹了。值得留意的是基本類型指針的切片,即上面的PtrStringSlice
字段,類型爲[]*string
。
因爲結構中存儲的是字符串指針,go-flags
在解析過程當中遇到該選項會自動建立字符串,將指針追加到切片中。
運行程序,傳入--pstrslice
選項:
$ ./main.exe --pstrslice test1 --pstrslice test2 slice of pointer of string flag: 0: test1 1: test2
另外,咱們能夠在選項中定義函數類型。該函數的惟一要求是有一個字符串類型的參數。解析中每次遇到該選項就會以選項值爲參數調用這個函數。
上面代碼中,Call
函數只是簡單的打印傳入的選項值。運行代碼,傳入--call
選項:
$ ./main.exe --call test1 --call test2 in callback: test1 in callback: test2
最後,go-flags
還支持 map 類型。雖然限制鍵必須是string
類型,值必須是基本類型,也能實現比較靈活的配置。map
類型的選項值中鍵-值經過:
分隔,如key:value
,可設置多個。運行代碼,傳入--intmap
選項:
$ ./main.exe --intmap key1:12 --intmap key2:58 int map: map[key1:12 key2:58]
go-flags
提供了很是多的設置選項,具體可參見文檔。這裏重點介紹兩個required
和default
。
required
非空時,表示對應的選項必須設置值,不然解析時返回ErrRequired
錯誤。
default
用於設置選項的默認值。若是已經設置了默認值,那麼required
是否設置並不影響,也就是說命令行參數中該選項能夠沒有。
看下面示例:
package main import ( "fmt" "log" "github.com/jessevdk/go-flags" ) type Option struct { Required string `short:"r" long:"required" required:"true"` Default string `short:"d" long:"default" default:"default"` } func main() { var opt Option _, err := flags.Parse(&opt) if err != nil { log.Fatal("Parse error:", err) } fmt.Println("required: ", opt.Required) fmt.Println("default: ", opt.Default) }
運行程序,不傳入default
選項,Default
字段取默認值,不傳入required
選項,執行報錯:
$ ./main.exe -r required-data required: required-data default: default $ ./main.exe -d default-data -r required-data required: required-data default: default-data $ ./main.exe the required flag `/r, /required' was not specified 2020/01/09 18:07:39 Parse error:the required flag `/r, /required' was not specified
package main import ( "fmt" "log" "os" "github.com/jessevdk/go-flags" ) type Option struct { Basic GroupBasicOption `description:"basic type" group:"basic"` Slice GroupSliceOption `description:"slice of basic type" group:"slice"` } type GroupBasicOption struct { IntFlag int `short:"i" long:"intflag" description:"int flag"` BoolFlag bool `short:"b" long:"boolflag" description:"bool flag"` FloatFlag float64 `short:"f" long:"floatflag" description:"float flag"` StringFlag string `short:"s" long:"stringflag" description:"string flag"` } type GroupSliceOption struct { IntSlice int `long:"intslice" description:"int slice"` BoolSlice bool `long:"boolslice" description:"bool slice"` FloatSlice float64 `long:"floatslice" description:"float slice"` StringSlice string `long:"stringslice" description:"string slice"` } func main() { var opt Option p := flags.NewParser(&opt, flags.Default) _, err := p.ParseArgs(os.Args[1:]) if err != nil { log.Fatal("Parse error:", err) } basicGroup := p.Command.Group.Find("basic") for _, option := range basicGroup.Options() { fmt.Printf("name:%s value:%v\n", option.LongNameWithNamespace(), option.Value()) } sliceGroup := p.Command.Group.Find("slice") for _, option := range sliceGroup.Options() { fmt.Printf("name:%s value:%v\n", option.LongNameWithNamespace(), option.Value()) } }
上面代碼中咱們將基本類型和它們的切片類型選項拆分到兩個結構體中,這樣可使代碼看起來更清晰天然,特別是在代碼量很大的狀況下。
這樣作還有一個好處,咱們試試用--help
運行該程序:
$ ./main.exe --help Usage: D:\code\golang\src\github.com\darjun\go-daily-lib\go-flags\group\main.exe [OPTIONS] basic: /i, /intflag: int flag /b, /boolflag bool flag /f, /floatflag: float flag /s, /stringflag: string flag slice: /intslice: int slice /boolslice bool slice /floatslice: float slice /stringslice: string slice Help Options: /? Show this help message /h, /help Show this help message
輸出的幫助信息中,也是按照咱們設定的分組顯示了,便於查看。
go-flags
支持子命令。咱們常常使用的 Go 和 Git 命令行程序就有大量的子命令。例如go version
、go build
、go run
、git status
、git commit
這些命令中version/build/run/status/commit
就是子命令。
使用go-flags
定義子命令比較簡單:
package main import ( "errors" "fmt" "log" "strconv" "strings" "github.com/jessevdk/go-flags" ) type MathCommand struct { Op string `long:"op" description:"operation to execute"` Args []string Result int64 } func (this *MathCommand) Execute(args []string) error { if this.Op != "+" && this.Op != "-" && this.Op != "x" && this.Op != "/" { return errors.New("invalid op") } for _, arg := range args { num, err := strconv.ParseInt(arg, 10, 64) if err != nil { return err } this.Result += num } this.Args = args return nil } type Option struct { Math MathCommand `command:"math"` } func main() { var opt Option _, err := flags.Parse(&opt) if err != nil { log.Fatal(err) } fmt.Printf("The result of %s is %d", strings.Join(opt.Math.Args, opt.Math.Op), opt.Math.Result) }
子命令必須實現go-flags
定義的Commander
接口:
type Commander interface { Execute(args []string) error }
解析命令行時,若是遇到不是以-
或--
開頭的參數,go-flags
會嘗試將其解釋爲子命令名。子命令的名字經過在結構標籤中使用command
指定。
子命令後面的參數都將做爲子命令的參數,子命令也能夠有選項。
上面代碼中,咱們實現了一個能夠計算任意個整數的加、減、乘、除子命令math
。
接下來看看如何使用:
$ ./main.exe math --op + 1 2 3 4 5 The result of 1+2+3+4+5 is 15 $ ./main.exe math --op - 1 2 3 4 5 The result of 1-2-3-4-5 is -13 $ ./main.exe math --op x 1 2 3 4 5 The result of 1x2x3x4x5 is 120 $ ./main.exe math --op ÷ 120 2 3 4 5 The result of 120÷2÷3÷4÷5 is 1
注意,不能使用乘法符號*
和除法符號/
,它們都不可識別。
go-flags
庫還有不少有意思的特性,例如支持 Windows 選項格式(/v
和/verbose
)、從環境變量中讀取默認值、從 ini 文件中讀取默認設置等等。你們有興趣能夠自行去研究~
歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~
本文由博客一文多發平臺 OpenWrite 發佈!