筆者在前文中介紹了 Golang 標準庫中 flag 包的用法,事實上有一個第三方的命令列引數解析包 pflag 比 flag 包使用的更為廣泛。pflag 包的設計目的就是替代標準庫中的 flag 包,因此它具有更強大的功能並且與標準的相容性更好。本文將介紹 pflag 包與 flag 包相比的主要優勢,如果你還不瞭解 flag 包的的用法,請參考《Golang : flag 包簡介》一文。本文的演示環境為 ubuntu 18.04。
pflag 包的主要特點
pflag 包與 flag 包的工作原理甚至是程式碼實現都是類似的,下面是 pflag 相對 flag 的一些優勢:
- 支援更加精細的引數型別:例如,flag 只支援 uint 和 uint64,而 pflag 額外支援 uint8、uint16、int32 等型別。
- 支援更多引數型別:ip、ip mask、ip net、count、以及所有型別的 slice 型別。
- 相容標準 flag 庫的 Flag 和 FlagSet:pflag 更像是對 flag 的擴充套件。
- 原生支援更豐富的功能:支援 shorthand、deprecated、hidden 等高階功能。
安裝 pflag 包
本文介紹 doker 原始碼中引用的 pflag 包 github.com/spf13/pfla,用下面的命令安裝該包:
$ go get github.com/spf13/pflag
入門 demo
在 Go workspace 的 src 目錄下建立 pflagdemo 目錄,並在目錄下建立 main.go 檔案,編輯其內容如下:
package main import flag "github.com/spf13/pflag" import ( "fmt" "strings" ) // 定義命令列引數對應的變數 var cliName = flag.StringP("name", "n", "nick", "Input Your Name") var cliAge = flag.IntP("age", "a",22, "Input Your Age") var cliGender = flag.StringP("gender", "g","male", "Input Your Gender") var cliOK = flag.BoolP("ok", "o", false, "Input Are You OK") var cliDes = flag.StringP("des-detail", "d", "", "Input Description") var cliOldFlag = flag.StringP("badflag", "b", "just for test", "Input badflag") func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName { from := []string{"-", "_"} to := "." for _, sep := range from { name = strings.Replace(name, sep, to, -1) } return flag.NormalizedName(name) } func main() { // 設定標準化引數名稱的函式 flag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc) // 為 age 引數設定 NoOptDefVal flag.Lookup("age").NoOptDefVal = "25" // 把 badflag 引數標記為即將廢棄的,請使用者使用 des-detail 引數 flag.CommandLine.MarkDeprecated("badflag", "please use --des-detail instead") // 把 badflag 引數的 shorthand 標記為即將廢棄的,請使用者使用 des-detail 的 shorthand 引數 flag.CommandLine.MarkShorthandDeprecated("badflag", "please use -d instead") // 在幫助文件中隱藏引數 gender flag.CommandLine.MarkHidden("badflag") // 把使用者傳遞的命令列引數解析為對應變數的值 flag.Parse() fmt.Println("name=", *cliName) fmt.Println("age=", *cliAge) fmt.Println("gender=", *cliGender) fmt.Println("ok=", *cliOK) fmt.Println("des=", *cliDes) }
程式碼本身很簡單,也新增了註釋,這裡就不再過多的解釋了。
執行 demo
在 flagdemo 目錄下執行 go build 命令編譯 demo 生成可執行檔案 flagdemo。下面我們通過執行 demo 程式來了解 pflag 包命令列引數的語法特點。
布林型別的引數
布林型別的引數有下面幾種寫法
--flag // 等同於 --flag=true --flag=value --flag value // 這種寫法只有在沒有設定預設值時才生效
NoOptDefVal 用法
pflag 包支援通過簡便的方式為引數設定預設值之外的值,實現方式為設定引數的 NoOptDefVal 屬性:
var cliAge = flag.IntP("age", "a",22, "Input Your Age") flag.Lookup("age").NoOptDefVal = "25"
下面是傳遞引數的方式和引數最終的取值:
Parsed Arguments Resulting Value --age=30 cliAge=30 --age cliAge=25 [nothing] cliAge=22
shorthand
與 flag 包不同,在 pflag 包中,選項名稱前面的 -- 和 - 是不一樣的。- 表示 shorthand,-- 表示完整的選項名稱。
除了最後一個 shorthand,其它的 shorthand 都必須是布林型別的引數或者是具有預設值的引數。
所以對於布林型別的引數和設定了 NoOptDefVal 的引數可以寫成下面的形式:
-o -o=true // 注意,下面的寫法是不正確的 -o true
非布林型別的引數和沒有設定 NoOptDefVal 的引數的寫法如下:
-g female -g=female -gfemale
日常的使用中一般會混合上面的兩類規則:
-aon "jack" -aon="jack" -aon"jack" -aonjack -oa=35
注意 -- 後面的引數不會被解析:
-oa=35 -- -gfemale
標準化引數的名稱
如果我們建立了名稱為 --des-detail 的引數,但是使用者卻在傳參時寫成了 --des_detail 或 --des.detail 會怎麼樣?預設情況下程式會報錯退出,但是我們可以通過 pflag 提供的 SetNormalizeFunc 功能輕鬆的解決這個問題:
func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName { from := []string{"-", "_"} to := "." for _, sep := range from { name = strings.Replace(name, sep, to, -1) } return flag.NormalizedName(name) } flag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
下面的寫法也能正確設定引數了:
--des_detail="person detail"
把引數標記為即將廢棄
在程式的不斷升級中新增新的引數和廢棄舊的引數都是常見的用例,pflag 包對廢棄引數也提供了很好的支援。通過 MarkDeprecated 和 MarkShorthandDeprecated 方法可以分別把引數及其 shorthand 標記為廢棄:
// 把 badflag 引數標記為即將廢棄的,請使用者使用 des-detail 引數 flag.CommandLine.MarkDeprecated("badflag", "please use --des-detail instead") // 把 badflag 引數的 shorthand 標記為即將廢棄的,請使用者使用 des-detail 的 shorthand 引數 flag.CommandLine.MarkShorthandDeprecated("badflag", "please use -d instead")
在幫助文件中隱藏引數
pflag 包還支援在引數說明中隱藏引數的功能:
// 在幫助文件中隱藏引數 badflag flag.CommandLine.MarkHidden("badflag")
看,幫助文件中沒有顯示 badflag 的資訊。其實在把引數標記為廢棄時,同時也會設定隱藏引數。
總結
正如本文中介紹的,pflag 包提供了很多非常棒的功能,這些功能方便了應用程式的開發者。因此越來越多的使用者拋棄標準庫中的 flag 包轉而使用 pflag 包解析命令列引數。
參考:
github spf13/pflag
Golang之使用Flag和Pflag
Golang命令列引數解析庫原始碼分析:flag VS pflag