golang flag包(命令行参数解析)
1.1 使用示例:
我們以?nginx?為例,執(zhí)行?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后續(xù)我們會(huì)利用flag包實(shí)現(xiàn)一個(gè)并發(fā)測(cè)試接口的程序。
現(xiàn)在我們來(lái)利用flag包簡(jiǎn)單實(shí)現(xiàn)一下nginx -h這個(gè)功能:
看不懂以上的代碼實(shí)現(xiàn)沒(méi)關(guān)系,先明確flag的能力,看完下面的講解回過(guò)頭來(lái)看就可以看懂了。
1.2 flag 包概述
flag 包實(shí)現(xiàn)了命令行參數(shù)的解析。
1.2.1 定義 flags 有兩種方式
1)flag.Xxx(),其中 Xxx 可以是 Int、String,Bool 等;返回一個(gè)相應(yīng)類型的指針,如:
var ip = flag.Int("flagname", 1234, "help message for flagname")
- 第一個(gè)參數(shù) :flag名稱為flagname
- 第二個(gè)參數(shù) :flagname默認(rèn)值為1234
- 第三個(gè)參數(shù) :flagname的提示信息
返回的ip是指針類型,所以這種方式獲取ip的值應(yīng)該fmt.Println(*ip)
2)flag.XxxVar(),將 flag 綁定到一個(gè)變量上,如:
var flagValue int flag.IntVar(&flagValue, "flagname", 1234, "help message for flagname")- 第一個(gè)參數(shù) :接收f(shuō)lagname的實(shí)際值的
- 第二個(gè)參數(shù) :flag名稱為flagname
- 第三個(gè)參數(shù) :flagname默認(rèn)值為1234
- 第四個(gè)參數(shù) :flagname的提示信息
這種方式獲取ip的值fmt.Println(ip)就可以了:
1.2.2 自定義 Value
另外,還可以創(chuàng)建自定義 flag,只要實(shí)現(xiàn) flag.Value 接口即可(要求?receiver?是指針),這時(shí)候可以通過(guò)如下方式定義該 flag:
flag.Var(&flagVal, "name", "help message for flagname")例如,解析我喜歡的編程語(yǔ)言,我們希望直接解析到 slice 中,我們可以定義如下 sliceValue類型,然后實(shí)現(xiàn)Value接口:
package mainimport ("flag""fmt""strings" )//定義一個(gè)類型,用于增加該類型方法 type sliceValue []string//new一個(gè)存放命令行參數(shù)值的slice func newSliceValue(vals []string, p *[]string) *sliceValue {*p = valsreturn (*sliceValue)(p) }/* Value接口: type Value interface {String() stringSet(string) error } 實(shí)現(xiàn)flag包中的Value接口,將命令行接收到的值用,分隔存到slice里 */ func (s *sliceValue) Set(val string) error {*s = sliceValue(strings.Split(val, ","))return nil }//flag為slice的默認(rèn)值default is me,和return返回值沒(méi)有關(guān)系 func (s *sliceValue) String() string {*s = sliceValue(strings.Split("default is me", ","))return "It's none of my business" }/* 可執(zhí)行文件名 -slice="java,go" 最后將輸出[java,go] 可執(zhí)行文件名 最后將輸出[default is me]*/ func main(){var languages []stringflag.Var(newSliceValue([]string{}, &languages), "slice", "I like programming `languages`")flag.Parse()//打印結(jié)果slice接收到的值fmt.Println(languages) }1.2.3 解析 flag
在所有的 flag 定義完成之后,可以通過(guò)調(diào)用?flag.Parse()?進(jìn)行解析。
命令行 flag 的語(yǔ)法有如下三種形式:
-flag // 只支持bool類型 -flag=x -flag x // 只支持非bool類型以上語(yǔ)法對(duì)于一個(gè)或兩個(gè)‘-’號(hào),效果是一樣的,但是要注意對(duì)于第三種情況,只能用于非 bool 類型的 flag。原因是:如果支持,那么對(duì)于這樣的命令 cmd -x *,如果有一個(gè)文件名字是:0或false等,則命令的原意會(huì)改變(bool 類型可以和其他類型一樣處理,其次 bool 類型支持 -flag 這種形式,因?yàn)镻arse()中,對(duì) bool 類型進(jìn)行了特殊處理)。默認(rèn)的,提供了 -flag,則對(duì)應(yīng)的值為 true,否則為 flag.Bool/BoolVar 中指定的默認(rèn)值;如果希望顯示設(shè)置為 false 則使用 -flag=false。
int 類型可以是十進(jìn)制、十六進(jìn)制、八進(jìn)制甚至是負(fù)數(shù);bool 類型可以是1, 0, t, f, true, false, TRUE, FALSE, True, False。Duration 可以接受任何 time.ParseDuration 能解析的類型。
- 注:如果bool類型的參數(shù)在命令行中用了-flag false這種形式時(shí),其后的參數(shù)都會(huì)被當(dāng)做非flag(non-flag)參數(shù),non-flag 參數(shù)后面解釋。
1.3 類型和函數(shù)
在看類型和函數(shù)之前,先看一下變量。
ErrHelp:該錯(cuò)誤類型用于當(dāng)命令行指定了 ·-help` 參數(shù)但沒(méi)有定義時(shí)。
例如1.2.2例子中:如果執(zhí)行時(shí)用了-help或者-h時(shí)就會(huì)輸出help message:
Usage of myflag.exe:-slice languagesI like programming languagesUsage:這是一個(gè)函數(shù),用于輸出所有定義了的命令行參數(shù)和幫助信息(usage message)。一般,當(dāng)命令行參數(shù)解析出錯(cuò)時(shí),該函數(shù)會(huì)被調(diào)用。我們可以指定自己的 Usage 函數(shù),即:flag.Usage = func(){}
1.3.1 函數(shù)
go標(biāo)準(zhǔn)庫(kù)中,經(jīng)常這么做:
定義了一個(gè)類型,提供了很多方法;為了方便使用,會(huì)實(shí)例化一個(gè)該類型的實(shí)例(通用),這樣便可以直接使用該實(shí)例調(diào)用方法。比如:encoding/base64 中提供了 StdEncoding 和 URLEncoding 實(shí)例,使用時(shí):base64.StdEncoding.Encode()
在 flag 包使用了有類似的方法,比如 CommandLine 變量,只不過(guò) flag 進(jìn)行了進(jìn)一步封裝:將 FlagSet 的方法都重新定義了一遍,也就是提供了一系列函數(shù),而函數(shù)中只是簡(jiǎn)單的調(diào)用已經(jīng)實(shí)例化好了的 FlagSet 實(shí)例:CommandLine 的方法。這樣,使用者是這么調(diào)用:flag.Parse() 而不是 flag. CommandLine.Parse()。(Go 1.2 起,將 CommandLine 導(dǎo)出,之前是非導(dǎo)出的)
這里不詳細(xì)介紹各個(gè)函數(shù),其他函數(shù)介紹可以參考astaxie的gopkg——flag章節(jié)。
1.3.2 類型(數(shù)據(jù)結(jié)構(gòu))
1)ErrorHandling
type ErrorHandling int該類型定義了在參數(shù)解析出錯(cuò)時(shí)錯(cuò)誤處理方式。定義了三個(gè)該類型的常量:
const (ContinueOnError ErrorHandling = iotaExitOnErrorPanicOnError )三個(gè)常量在源碼的 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 類型代表一個(gè) flag 的狀態(tài)。
比如,對(duì)于命令:./nginx -c /etc/nginx.conf,相應(yīng)代碼是:
flag.StringVar(&c, "c", "conf/nginx.conf", "set configuration `file`")則該 Flag 實(shí)例(可以通過(guò)?flag.Lookup("c")?獲得)相應(yīng)各個(gè)字段的值為:
&Flag{Name: c,Usage: set configuration file,Value: /etc/nginx.conf,DefValue: conf/nginx.conf, }Lookup函數(shù):獲取flag集合中名稱為name值的flag指針,如果對(duì)應(yīng)的flag不存在,返回nil
示例:
運(yùn)行結(jié)果:
// ./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 // 是否執(zhí)行過(guò)Parse()actual map[string]*Flag // 存放實(shí)際傳遞了的參數(shù)(即命令行參數(shù))formal map[string]*Flag // 存放所有已定義命令行參數(shù)args []string // arguments after flags // 開(kāi)始存放所有參數(shù),最后保留 非flag(non-flag)參數(shù)exitOnError bool // does the program exit if there's an error?errorHandling ErrorHandling // 當(dāng)解析出錯(cuò)時(shí),處理錯(cuò)誤的方式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 }所有參數(shù)類型需要實(shí)現(xiàn) Value 接口,flag 包中,為int、float、bool等實(shí)現(xiàn)了該接口。借助該接口,我們可以自定義flag。(上文已經(jīng)給了具體的例子)
1.4 主要類型的方法(包括類型實(shí)例化)
flag 包中主要是 FlagSet 類型。
1.4.1 實(shí)例化方式
NewFlagSet() 用于實(shí)例化 FlagSet。預(yù)定義的 FlagSet 實(shí)例 CommandLine 的定義方式:
// The default set of command-line flags, parsed from os.Args. var CommandLine = NewFlagSet(os.Args[0], ExitOnError)可見(jiàn),默認(rèn)的 FlagSet 實(shí)例在解析出錯(cuò)時(shí)會(huì)退出程序。
由于 FlagSet 中的字段沒(méi)有 export,其他方式獲得 FlagSet實(shí)例后,比如:FlagSet{} 或 new(FlagSet),應(yīng)該調(diào)用Init() 方法,以初始化 name 和 errorHandling,否則 name 為空,errorHandling 為 ContinueOnError(errorHandling默認(rèn)為0)。
1.4.2 定義 flag 參數(shù)的方法
這一系列的方法都有兩種形式,在一開(kāi)始已經(jīng)說(shuō)了兩種方式的區(qū)別。這些方法用于定義某一類型的 flag 參數(shù)。
1.4.3 解析參數(shù)(Parse)
func (f *FlagSet) Parse(arguments []string) error從參數(shù)列表中解析定義的 flag。方法參數(shù) arguments 不包括命令名,即應(yīng)該是os.Args[1:]。事實(shí)上,flag.Parse()?函數(shù)就是這么做的:
// 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:]) }該方法應(yīng)該在 flag 參數(shù)定義后而具體參數(shù)值被訪問(wèn)前調(diào)用。
如果提供了 -help 參數(shù)(命令中給了)但沒(méi)有定義(代碼中沒(méi)有),該方法返回 ErrHelp 錯(cuò)誤。默認(rèn)的 CommandLine,在 Parse 出錯(cuò)時(shí)會(huì)退出程序(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 }真正解析參數(shù)的方法是非導(dǎo)出方法?parseOne。
結(jié)合?parseOne?方法,我們來(lái)解釋?non-flag?以及包文檔中的這句話:
Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".
我們需要了解解析什么時(shí)候停止。
根據(jù) Parse() 中 for 循環(huán)終止的條件(不考慮解析出錯(cuò)),我們知道,當(dāng) parseOne 返回 false, nil 時(shí),Parse 解析終止。正常解析完成我們不考慮。看一下 parseOne 的源碼發(fā)現(xiàn),有三處會(huì)返回 false, nil。
在這里先說(shuō)一下non-flag命令行參數(shù)是指不滿足命令行語(yǔ)法的參數(shù),如命令行參數(shù)為cmd -flag=true abc則第一個(gè)非flag命令行參數(shù)為“abc”
1)參數(shù)列表長(zhǎng)度為0
if len(f.args) == 0 {return false, nil }2)第一個(gè) non-flag 參數(shù)
s := f.args[0] if len(s) == 0 || s[0] != '-' || len(s) == 1 {return false, nil }也就是,當(dāng)遇到單獨(dú)的一個(gè)"-"或不是"-"開(kāi)始時(shí),會(huì)停止解析。比如:
./nginx - 或 ./nginx ba或者./nginx這兩種情況,-c?都不會(huì)被正確解析。像該例子中的"-"或ba(以及之后的參數(shù)),我們稱之為?non-flag參數(shù)。
3)兩個(gè)連續(xù)的"--"
if s[1] == '-' {num_minuses++if len(s) == 2 { // "--" terminates the flagsf.args = f.args[1:]return false, nil} }也就是,當(dāng)遇到連續(xù)的兩個(gè)"-"時(shí),解析停止。如:
./nginx --*下面這種情況是可以正常解析的:
./nginx -c --這里的"--"會(huì)被當(dāng)成是 c 的值
parseOne 方法中接下來(lái)是處理 -flag=x 這種形式,然后是 -flag 這種形式(bool類型)(這里對(duì)bool進(jìn)行了特殊處理),接著是 -flag x 這種形式,最后,將解析成功的 Flag 實(shí)例存入 FlagSet 的 actual map 中。
另外,在 parseOne 中有這么一句:
也就是說(shuō),每執(zhí)行成功一次 parseOne,f.args 會(huì)少一個(gè)。所以,FlagSet 中的 args 最后留下來(lái)的就是所有 non-flag 參數(shù)。
1.4.4 Arg(i int) 和 Args()、NArg()、NFlag()
Arg(i int) 和 Args() 這兩個(gè)方法就是獲取 non-flag 參數(shù)的;NArg()獲得 non-flag 的個(gè)數(shù);NFlag() 獲得 FlagSet 中 actual 長(zhǎng)度(即被設(shè)置了的參數(shù)個(gè)數(shù))。
1.4.5 Visit/VisitAll
這兩個(gè)函數(shù)分別用于訪問(wèn) FlatSet 的 actual(存放參數(shù)值實(shí)際Flag的map) 和 formal(存放參數(shù)名默認(rèn)Flag的map) 中的 Flag,而具體的訪問(wèn)方式由調(diào)用者決定。
具體使用demo見(jiàn):
func (f FlagSet) Visit(fn func(Flag))
func (f FlagSet) VisitAll(fn func(Flag))
1.4.6 PrintDefaults()
打印所有已定義參數(shù)的默認(rèn)值(調(diào)用 VisitAll 實(shí)現(xiàn)),默認(rèn)輸出到標(biāo)準(zhǔn)錯(cuò)誤,除非指定了 FlagSet 的 output(通過(guò)SetOutput() 設(shè)置)。
在1.1示例中有使用。還可以參考:
func PrintDefaults()
1.4.7 Set(name, value string)
將名稱為name的flag的值設(shè)置為value, 成功返回nil。
demo請(qǐng)見(jiàn):
func Set(name, value string) error
1.5 總結(jié)
使用建議:雖然上面講了那么多,一般來(lái)說(shuō),我們只簡(jiǎn)單的定義flag,然后 parse,就如同開(kāi)始的例子一樣。
如果項(xiàng)目需要復(fù)雜或更高級(jí)的命令行解析方式,可以使用 https://github.com/urfave/cli 或者 https://github.com/spf13/cobra 這兩個(gè)強(qiáng)大的庫(kù)。
總結(jié)
以上是生活随笔為你收集整理的golang flag包(命令行参数解析)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python3 获取当前路径,当前文件名
- 下一篇: PyMongo官方文档翻译——VNPY