《Go語言四十二章經》第三十四章 命令列 flag 包
《Go語言四十二章經》第三十四章 命令列 flag 包
作者:李驍
34.1 命令列
寫命令列程式時需要對命令引數進行解析,這時我們可以使用os庫。os可以通過變數Args來獲取命令引數,os.Args返回一個字串陣列,其中第一個引數就是執行檔案本身。
package main import ( "fmt" "os" ) func main() { fmt.Println(os.Args) }
編譯執行後執行
$ ./cmd -user="root" [./cmd -user=root]
這種方式操作起來要自己封裝,比較費時費勁。
34.2 flag包
Go提供了flag庫,可以很方便的操作命名行引數,下面介紹下flag的用法。
幾個概念:
1)命令列引數(或引數):是指執行程式提供的引數
2)已定義命令列引數:是指程式中通過flag.Xxx等這種形式定義了的引數
3)非flag(non-flag)命令列引數(或保留的命令列引數):先可以簡單理解為flag包不能解析的引數
package main import ( "flag" "fmt" "os" ) var ( h, H bool v bool q *bool Dstring Conf string ) func init() { flag.BoolVar(&h, "h", false, "幫助資訊") flag.BoolVar(&h, "H", false, "幫助資訊") flag.BoolVar(&v, "v", false, "顯示版本號") // flag.StringVar(&D, "D", "deamon", "set descripton ") flag.StringVar(&Conf, "Conf", "/dev/conf/cli.conf", "set Conf filename ") // 另一種繫結方式 q = flag.Bool("q", false, "退出程式") // 像flag.Xxx函式格式都是一樣的,第一個引數表示引數名稱, // 第二個引數表示預設值,第三個引數表示使用說明和描述。 // flag.XxxVar這樣的函式第一個引數換成了變數地址, // 後面的引數和flag.Xxx是一樣的。 // 改變預設的 Usage flag.Usage = usage flag.Parse() var cmd string = flag.Arg(0) fmt.Printf("-----------------------\n") fmt.Printf("cli non=flags: %s\n", cmd) fmt.Printf("q: %b\n", *q) fmt.Printf("descripton:%s\n", D) fmt.Printf("Conf filename : %s\n", Conf) fmt.Printf("-----------------------\n") fmt.Printf("there are %d non-flag input param\n", flag.NArg()) for i, param := range flag.Args() { fmt.Printf("#%d:%s\n", i, param) } } func main() { flag.Parse() if h || H { flag.Usage() } } func usage() { fmt.Fprintf(os.Stderr, `CLI: 8.0 Usage: Cli [-hvq] [-D descripton] [-Conf filename] `) flag.PrintDefaults() }
flag包實現了命令列引數的解析,大致需要幾個步驟:
一:flag引數定義或繫結
定義flags有兩種方式:
1)flag.Xxx(),其中Xxx可以是Int、String等;返回一個相應型別的指標,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
2)flag.XxxVar(),將flag繫結到一個變數上,如:
var flagvar int flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
另外,還可以建立自定義flag,只要實現flag.Value介面即可(要求receiver是指標),這時候可以通過如下方式定義該flag:
flag.Var(&flagVal, "name", "help message for flagname")
命令列flag的語法有如下三種形式: -flag // 只支援bool型別 -flag=x -flag x // 只支援非bool型別
二:flag引數解析
在所有的flag定義完成之後,可以通過呼叫flag.Parse()進行解析。
根據Parse()中for迴圈終止的條件,當parseOne返回false,nil時,Parse解析終止。
s := f.args[0] if len(s) == 0 || s[0] != '-' || len(s) == 1 { return false, nil }
當遇到單獨的一個“-”或不是“-”開始時,會停止解析。比如:./cli – -f 或 ./cli -f
這兩種情況,-f都不會被正確解析。像這些引數,我們稱之為non-flag引數
parseOne方法中接下來是處理-flag=x,然後是-flag(bool型別)(這裡對bool進行了特殊處理),接著是-flag x這種形式,最後,將解析成功的Flag例項存入FlagSet的actual map中。
Arg(i int)和Args()、NArg()、NFlag() Arg(i int)和Args()這兩個方法就是獲取non-flag引數的;NArg()獲得non-flag個數;NFlag()獲得FlagSet中actual長度(即被設定了的引數個數)。
flag解析遇到non-flag引數就停止了。所以如果我們將non-flag引數放在最前面,flag什麼也不會解析,因為flag遇到了這個就停止解析了。
三:分支程式
根據引數值,程式碼進入分支程式,執行相關功能。上面程式碼提供了 -h 引數的功能執行。
if h || H { flag.Usage() }
總體而言,從例子上看,flag package很有用,但是並沒有強大到解析一切的程度。如果你的入參解析非常複雜,flag可能捉襟見肘。
Cobra是一個用來建立強大的現代CLI命令列的Go開源庫。開源包可能比較合適構建更為複雜的命令列程式。開源地址:https://github.com/spf13/cobra
本書《Go語言四十二章經》內容在github上同步地址:https://github.com/ffhelicopter/Go42
本書《Go語言四十二章經》內容在簡書同步地址:https://www.jianshu.com/nb/29056963
雖然本書中例子都經過實際執行,但難免出現錯誤和不足之處,煩請您指出;如有建議也歡迎交流。