golang flag包(命令行参数解析)
1.1 使用示例:
我們以?nginx?為例,執行?nginx -h,輸出如下:
nginx version: nginx/1.10.0 Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]Options:-?,-h : this help-v : show version and exit-V : show version and configure options then exit-t : test configuration and exit-T : test configuration, dump it and exit-q : suppress non-error messages during configuration testing-s signal : send signal to a master process: stop, quit, reopen, reload-p prefix : set prefix path (default: /usr/local/nginx/)-c filename : set configuration file (default: conf/nginx.conf)-g directives : set global directives out of configuration file后續我們會利用flag包實現一個并發測試接口的程序。
現在我們來利用flag包簡單實現一下nginx -h這個功能:
看不懂以上的代碼實現沒關系,先明確flag的能力,看完下面的講解回過頭來看就可以看懂了。
1.2 flag 包概述
flag 包實現了命令行參數的解析。
1.2.1 定義 flags 有兩種方式
1)flag.Xxx(),其中 Xxx 可以是 Int、String,Bool 等;返回一個相應類型的指針,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
- 第一個參數 :flag名稱為flagname
- 第二個參數 :flagname默認值為1234
- 第三個參數 :flagname的提示信息
返回的ip是指針類型,所以這種方式獲取ip的值應該fmt.Println(*ip)
2)flag.XxxVar(),將 flag 綁定到一個變量上,如:
var flagValue int flag.IntVar(&flagValue, "flagname", 1234, "help message for flagname")- 第一個參數 :接收flagname的實際值的
- 第二個參數 :flag名稱為flagname
- 第三個參數 :flagname默認值為1234
- 第四個參數 :flagname的提示信息
這種方式獲取ip的值fmt.Println(ip)就可以了:
1.2.2 自定義 Value
另外,還可以創建自定義 flag,只要實現 flag.Value 接口即可(要求?receiver?是指針),這時候可以通過如下方式定義該 flag:
flag.Var(&flagVal, "name", "help message for flagname")例如,解析我喜歡的編程語言,我們希望直接解析到 slice 中,我們可以定義如下 sliceValue類型,然后實現Value接口:
package mainimport ("flag""fmt""strings" )//定義一個類型,用于增加該類型方法 type sliceValue []string//new一個存放命令行參數值的slice func newSliceValue(vals []string, p *[]string) *sliceValue {*p = valsreturn (*sliceValue)(p) }/* Value接口: type Value interface {String() stringSet(string) error } 實現flag包中的Value接口,將命令行接收到的值用,分隔存到slice里 */ func (s *sliceValue) Set(val string) error {*s = sliceValue(strings.Split(val, ","))return nil }//flag為slice的默認值default is me,和return返回值沒有關系 func (s *sliceValue) String() string {*s = sliceValue(strings.Split("default is me", ","))return "It's none of my business" }/* 可執行文件名 -slice="java,go" 最后將輸出[java,go] 可執行文件名 最后將輸出[default is me]*/ func main(){var languages []stringflag.Var(newSliceValue([]string{}, &languages), "slice", "I like programming `languages`")flag.Parse()//打印結果slice接收到的值fmt.Println(languages) }1.2.3 解析 flag
在所有的 flag 定義完成之后,可以通過調用?flag.Parse()?進行解析。
命令行 flag 的語法有如下三種形式:
-flag // 只支持bool類型 -flag=x -flag x // 只支持非bool類型以上語法對于一個或兩個‘-’號,效果是一樣的,但是要注意對于第三種情況,只能用于非 bool 類型的 flag。原因是:如果支持,那么對于這樣的命令 cmd -x *,如果有一個文件名字是:0或false等,則命令的原意會改變(bool 類型可以和其他類型一樣處理,其次 bool 類型支持 -flag 這種形式,因為Parse()中,對 bool 類型進行了特殊處理)。默認的,提供了 -flag,則對應的值為 true,否則為 flag.Bool/BoolVar 中指定的默認值;如果希望顯示設置為 false 則使用 -flag=false。
int 類型可以是十進制、十六進制、八進制甚至是負數;bool 類型可以是1, 0, t, f, true, false, TRUE, FALSE, True, False。Duration 可以接受任何 time.ParseDuration 能解析的類型。
- 注:如果bool類型的參數在命令行中用了-flag false這種形式時,其后的參數都會被當做非flag(non-flag)參數,non-flag 參數后面解釋。
1.3 類型和函數
在看類型和函數之前,先看一下變量。
ErrHelp:該錯誤類型用于當命令行指定了 ·-help` 參數但沒有定義時。
例如1.2.2例子中:如果執行時用了-help或者-h時就會輸出help message:
Usage of myflag.exe:-slice languagesI like programming languagesUsage:這是一個函數,用于輸出所有定義了的命令行參數和幫助信息(usage message)。一般,當命令行參數解析出錯時,該函數會被調用。我們可以指定自己的 Usage 函數,即:flag.Usage = func(){}
1.3.1 函數
go標準庫中,經常這么做:
定義了一個類型,提供了很多方法;為了方便使用,會實例化一個該類型的實例(通用),這樣便可以直接使用該實例調用方法。比如:encoding/base64 中提供了 StdEncoding 和 URLEncoding 實例,使用時:base64.StdEncoding.Encode()
在 flag 包使用了有類似的方法,比如 CommandLine 變量,只不過 flag 進行了進一步封裝:將 FlagSet 的方法都重新定義了一遍,也就是提供了一系列函數,而函數中只是簡單的調用已經實例化好了的 FlagSet 實例:CommandLine 的方法。這樣,使用者是這么調用:flag.Parse() 而不是 flag. CommandLine.Parse()。(Go 1.2 起,將 CommandLine 導出,之前是非導出的)
這里不詳細介紹各個函數,其他函數介紹可以參考astaxie的gopkg——flag章節。
1.3.2 類型(數據結構)
1)ErrorHandling
type ErrorHandling int該類型定義了在參數解析出錯時錯誤處理方式。定義了三個該類型的常量:
const (ContinueOnError ErrorHandling = iotaExitOnErrorPanicOnError )三個常量在源碼的 FlagSet 的方法 parseOne() 中使用了。
2)Flag
// A Flag represents the state of a flag. type Flag struct {Name string // name as it appears on command lineUsage string // help messageValue Value // value as setDefValue string // default value (as text); for usage message }Flag 類型代表一個 flag 的狀態。
比如,對于命令:./nginx -c /etc/nginx.conf,相應代碼是:
flag.StringVar(&c, "c", "conf/nginx.conf", "set configuration `file`")則該 Flag 實例(可以通過?flag.Lookup("c")?獲得)相應各個字段的值為:
&Flag{Name: c,Usage: set configuration file,Value: /etc/nginx.conf,DefValue: conf/nginx.conf, }Lookup函數:獲取flag集合中名稱為name值的flag指針,如果對應的flag不存在,返回nil
示例:
運行結果:
// ./testlookup -test "12345" test:default value test1:<nil> test:12345 test1:<nil>3)FlagSet
// A FlagSet represents a set of defined flags. type FlagSet struct {// Usage is the function called when an error occurs while parsing flags.// The field is a function (not a method) that may be changed to point to// a custom error handler.Usage func()name string // FlagSet的名字。CommandLine 給的是 os.Args[0]parsed bool // 是否執行過Parse()actual map[string]*Flag // 存放實際傳遞了的參數(即命令行參數)formal map[string]*Flag // 存放所有已定義命令行參數args []string // arguments after flags // 開始存放所有參數,最后保留 非flag(non-flag)參數exitOnError bool // does the program exit if there's an error?errorHandling ErrorHandling // 當解析出錯時,處理錯誤的方式output io.Writer // nil means stderr; use out() accessor }4)Value 接口
// Value is the interface to the dynamic value stored in a flag. // (The default value is represented as a string.) type Value interface {String() stringSet(string) error }所有參數類型需要實現 Value 接口,flag 包中,為int、float、bool等實現了該接口。借助該接口,我們可以自定義flag。(上文已經給了具體的例子)
1.4 主要類型的方法(包括類型實例化)
flag 包中主要是 FlagSet 類型。
1.4.1 實例化方式
NewFlagSet() 用于實例化 FlagSet。預定義的 FlagSet 實例 CommandLine 的定義方式:
// The default set of command-line flags, parsed from os.Args. var CommandLine = NewFlagSet(os.Args[0], ExitOnError)可見,默認的 FlagSet 實例在解析出錯時會退出程序。
由于 FlagSet 中的字段沒有 export,其他方式獲得 FlagSet實例后,比如:FlagSet{} 或 new(FlagSet),應該調用Init() 方法,以初始化 name 和 errorHandling,否則 name 為空,errorHandling 為 ContinueOnError(errorHandling默認為0)。
1.4.2 定義 flag 參數的方法
這一系列的方法都有兩種形式,在一開始已經說了兩種方式的區別。這些方法用于定義某一類型的 flag 參數。
1.4.3 解析參數(Parse)
func (f *FlagSet) Parse(arguments []string) error從參數列表中解析定義的 flag。方法參數 arguments 不包括命令名,即應該是os.Args[1:]。事實上,flag.Parse()?函數就是這么做的:
// Parse parses the command-line flags from os.Args[1:]. Must be called // after all flags are defined and before flags are accessed by the program. func Parse() {// Ignore errors; CommandLine is set for ExitOnError.CommandLine.Parse(os.Args[1:]) }該方法應該在 flag 參數定義后而具體參數值被訪問前調用。
如果提供了 -help 參數(命令中給了)但沒有定義(代碼中沒有),該方法返回 ErrHelp 錯誤。默認的 CommandLine,在 Parse 出錯時會退出程序(ExitOnError)。
為了更深入的理解,我們看一下 Parse(arguments []string) 的源碼:
func (f *FlagSet) Parse(arguments []string) error {f.parsed = truef.args = argumentsfor {seen, err := f.parseOne()if seen {continue}if err == nil {break}switch f.errorHandling {case ContinueOnError:return errcase ExitOnError:os.Exit(2)case PanicOnError:panic(err)}}return nil }真正解析參數的方法是非導出方法?parseOne。
結合?parseOne?方法,我們來解釋?non-flag?以及包文檔中的這句話:
Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".
我們需要了解解析什么時候停止。
根據 Parse() 中 for 循環終止的條件(不考慮解析出錯),我們知道,當 parseOne 返回 false, nil 時,Parse 解析終止。正常解析完成我們不考慮。看一下 parseOne 的源碼發現,有三處會返回 false, nil。
在這里先說一下non-flag命令行參數是指不滿足命令行語法的參數,如命令行參數為cmd -flag=true abc則第一個非flag命令行參數為“abc”
1)參數列表長度為0
if len(f.args) == 0 {return false, nil }2)第一個 non-flag 參數
s := f.args[0] if len(s) == 0 || s[0] != '-' || len(s) == 1 {return false, nil }也就是,當遇到單獨的一個"-"或不是"-"開始時,會停止解析。比如:
./nginx - 或 ./nginx ba或者./nginx這兩種情況,-c?都不會被正確解析。像該例子中的"-"或ba(以及之后的參數),我們稱之為?non-flag參數。
3)兩個連續的"--"
if s[1] == '-' {num_minuses++if len(s) == 2 { // "--" terminates the flagsf.args = f.args[1:]return false, nil} }也就是,當遇到連續的兩個"-"時,解析停止。如:
./nginx --*下面這種情況是可以正常解析的:
./nginx -c --這里的"--"會被當成是 c 的值
parseOne 方法中接下來是處理 -flag=x 這種形式,然后是 -flag 這種形式(bool類型)(這里對bool進行了特殊處理),接著是 -flag x 這種形式,最后,將解析成功的 Flag 實例存入 FlagSet 的 actual map 中。
另外,在 parseOne 中有這么一句:
也就是說,每執行成功一次 parseOne,f.args 會少一個。所以,FlagSet 中的 args 最后留下來的就是所有 non-flag 參數。
1.4.4 Arg(i int) 和 Args()、NArg()、NFlag()
Arg(i int) 和 Args() 這兩個方法就是獲取 non-flag 參數的;NArg()獲得 non-flag 的個數;NFlag() 獲得 FlagSet 中 actual 長度(即被設置了的參數個數)。
1.4.5 Visit/VisitAll
這兩個函數分別用于訪問 FlatSet 的 actual(存放參數值實際Flag的map) 和 formal(存放參數名默認Flag的map) 中的 Flag,而具體的訪問方式由調用者決定。
具體使用demo見:
func (f FlagSet) Visit(fn func(Flag))
func (f FlagSet) VisitAll(fn func(Flag))
1.4.6 PrintDefaults()
打印所有已定義參數的默認值(調用 VisitAll 實現),默認輸出到標準錯誤,除非指定了 FlagSet 的 output(通過SetOutput() 設置)。
在1.1示例中有使用。還可以參考:
func PrintDefaults()
1.4.7 Set(name, value string)
將名稱為name的flag的值設置為value, 成功返回nil。
demo請見:
func Set(name, value string) error
1.5 總結
使用建議:雖然上面講了那么多,一般來說,我們只簡單的定義flag,然后 parse,就如同開始的例子一樣。
如果項目需要復雜或更高級的命令行解析方式,可以使用 https://github.com/urfave/cli 或者 https://github.com/spf13/cobra 這兩個強大的庫。
總結
以上是生活随笔為你收集整理的golang flag包(命令行参数解析)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 获取当前路径,当前文件名
- 下一篇: python时间的转换及比较