深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用_第1頁(yè)
深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用_第2頁(yè)
深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用_第3頁(yè)
深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用_第4頁(yè)
深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用_第5頁(yè)
已閱讀5頁(yè),還剩14頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

第深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用目錄1.使用1.1示例1.2標(biāo)志類型1.3標(biāo)志語(yǔ)法2.源碼解讀2.1定義標(biāo)志2.2解析標(biāo)志參數(shù)2.3其他代碼3.總結(jié)在使用Go進(jìn)行開(kāi)發(fā)的過(guò)程中,命令行參數(shù)解析是我們經(jīng)常遇到的需求。而flag包正是一個(gè)用于實(shí)現(xiàn)命令行參數(shù)解析的Go標(biāo)準(zhǔn)庫(kù)。在本文中,我們將深入探討flag標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)原理和使用技巧,以幫助讀者更好地理解和掌握該庫(kù)的使用方法。

1.使用

1.1示例

flag基本使用示例代碼如下:

packagemain

import(

"flag"

"fmt"

typeflagValstruct{

valstring

func(v*flagVal)String()string{

returnv.val

func(v*flagVal)Set(sstring)error{

v.val=s

returnnil

funcmain(){

//1.使用flag.Type()返回*int類型命令行參數(shù)

varnFlag=flag.Int("n",1234,"helpmessageforflagn")

//2.使用flag.TypeVar()綁定命令行參數(shù)到int類型變量

varflagvarint

flag.IntVar(flagvar,"flagvar",1234,"helpmessageforflagvar")

//3.使用flag.Var()綁定命令行參數(shù)到實(shí)現(xiàn)了flag.Value接口的自定義類型變量

val:=flagVal{}

flag.Var(val,"val","helpmessageforval")

//解析命令行參數(shù)

flag.Parse()

fmt.Printf("nFlag:%d\n",*nFlag)

fmt.Printf("flagvar:%d\n",flagvar)

fmt.Printf("val:%+v\n",val)

fmt.Printf("NFlag:%v\n",flag.NFlag())//返回已設(shè)置的命令行標(biāo)志個(gè)數(shù)

fmt.Printf("NArg:%v\n",flag.NArg())//返回處理完標(biāo)志后剩余的參數(shù)個(gè)數(shù)

fmt.Printf("Args:%v\n",flag.Args())//返回處理完標(biāo)志后剩余的參數(shù)列表

fmt.Printf("Arg(1):%v\n",flag.Arg(1))//返回處理完標(biāo)志后剩余的參數(shù)列表中第i項(xiàng)

}

可以通過(guò)指定--help/-h參數(shù)來(lái)查看這個(gè)命令行程序的使用幫助:

$gorunmain.go-h

Usageof./main:

-flagvarint

helpmessageforflagvar(default1234)

-nint

helpmessageforflagn(default1234)

-valvalue

helpmessageforval

這個(gè)程序接收三個(gè)命令行參數(shù):

int類型的-flagvar,默認(rèn)值為2134。int類型的-n,默認(rèn)值為2134。value類型的-val,無(wú)默認(rèn)值。

我們可以將-flagvar、-n、-val稱作flag,即「標(biāo)志」,這也是Go內(nèi)置命令行參數(shù)解析庫(kù)被命名為flag的原因,見(jiàn)名知意。

這三個(gè)參數(shù)在示例代碼中,分別使用了三種不同形式來(lái)指定:

flag.Type():

-n標(biāo)志是使用varnFlag=flag.Int(n,1234,helpmessageforflagn)來(lái)指定的。

flag.Int函數(shù)簽名如下:

funcInt(namestring,valueint,usagestring)*int

flag.Int函數(shù)接收三個(gè)參數(shù),分別是標(biāo)志名稱、標(biāo)志默認(rèn)參數(shù)值、標(biāo)志使用幫助信息。函數(shù)最終還會(huì)返回一個(gè)*int類型的值,表示用戶在執(zhí)行命令行程序時(shí)為這個(gè)標(biāo)志指定的參數(shù)。

除了使用flag.Int來(lái)設(shè)置int類型標(biāo)志,flag還支持其他多種類型,如使用flag.String來(lái)設(shè)置string類型標(biāo)志。

flag.TypeVar():

-flagvar標(biāo)志是使用flag.IntVar(flagvar,flagvar,1234,helpmessageforflagvar)來(lái)指定的。

flag.IntVar函數(shù)簽名如下:

funcIntVar(p*int,namestring,valueint,usagestring)

與flag.Int不同的是,flag.IntVar函數(shù)取消了返回值,而是會(huì)將用戶傳遞的命令行參數(shù)綁定到第一個(gè)參數(shù)p*int。

除了使用flag.IntVar來(lái)綁定int類型參數(shù)到標(biāo)志,flag還提供其他多個(gè)函數(shù)來(lái)支持綁定不同類型參數(shù)到標(biāo)志,如使用flag.StringVar來(lái)綁定string類型標(biāo)志。

flag.Var():

-val標(biāo)志是使用flag.Var(val,val,helpmessageforval)來(lái)指定的。

flag.Var函數(shù)簽名如下:

funcVar(valueValue,namestring,usagestring)

flag.Var函數(shù)接收三個(gè)參數(shù),后兩個(gè)參數(shù)分別是標(biāo)志名稱、標(biāo)志使用幫助信息。而用戶傳遞的命令行參數(shù)將被綁定到第一個(gè)參數(shù)value。

typeValueinterface{

String()string

Set(string)error

}

我們可以自定義類型,只要實(shí)現(xiàn)了flag.Value接口,都可以傳遞給flag.Var,這極大的增加了flag包的靈活性。

定義完三個(gè)標(biāo)志,我們還需要使用flag.Parse()來(lái)解析命令行參數(shù),只有解析成功以后,才會(huì)將戶傳遞的命令行參數(shù)值綁定到對(duì)應(yīng)的標(biāo)志變量中。之后就可以使用nFlag、flagvar、val的變量值了。

在main函數(shù)底部,使用flag.NFlag()、flag.NArg()、flag.Args()、flag.Arg(1)幾個(gè)函數(shù)獲取并展示了命令行參數(shù)相關(guān)信息。

現(xiàn)在我們嘗試給這個(gè)命令行程序傳遞幾個(gè)參數(shù)并執(zhí)行它,看下輸出結(jié)果:

$gorunmain.go-n100-valtestabcd

nFlag:100

flagvar:1234

val:{val:test}

NFlag:2

NArg:4

Args:[abcd]

Arg(1):b

我們通過(guò)-n100為-n標(biāo)志指定了參數(shù)值100,最終會(huì)被賦值給nFlag變量。

由于沒(méi)有指定flagvar標(biāo)志的參數(shù)值,所以flagvar變量會(huì)被賦予默認(rèn)值1234。

接著,我們又通過(guò)-valtest為-val標(biāo)志指定了參數(shù)值test,最終賦值給了自定義的flagVal結(jié)構(gòu)體的val字段。

因?yàn)橹辉O(shè)置了-n和-val兩個(gè)標(biāo)志的參數(shù)值,所以函數(shù)flag.NFlag()返回結(jié)果為2。

abcd四個(gè)參數(shù)由于沒(méi)有被定義,所以flag.NArg()返回結(jié)果為4。

flag.Args()返回的切片中存儲(chǔ)了abcd四個(gè)參數(shù)。

flag.Arg(1)返回切片中下標(biāo)為1位置的參數(shù),即b。

1.2標(biāo)志類型

在上面的示例中,我們展示了int類型和自定義的flag.Value的使用,flag包支持的所有標(biāo)志類型匯總?cè)缦拢?/p>

參數(shù)類型合法值boolstrconv.ParseBool能夠解析的有效值,接受:1,0,t,f,T,F,true,false,TRUE,FALSE,True,False。time.Durationtime.ParseDuration能夠解析的有效值,如:300ms,-1.5hor2h45m,合法單位:ns,us(ors),ms,s,m,h。float64合法的浮點(diǎn)數(shù)類型。int/int64/uint/uint64合法的整數(shù)類型,如:1234,0664,0x1234,也可以是負(fù)數(shù)。string合法的字符串類型。flag.Value實(shí)現(xiàn)了該接口的類型。

除了支持幾種Go默認(rèn)的原生類型外,如果我們想實(shí)現(xiàn)其他類型標(biāo)志的定義,都可以通過(guò)flag.Value接口類型來(lái)完成。其實(shí)flag包內(nèi)部對(duì)于bool、int等所有類型的定義,都實(shí)現(xiàn)了flag.Value接口,在稍后講解源碼過(guò)程中將會(huì)有所體現(xiàn)。

1.3標(biāo)志語(yǔ)法

命令行標(biāo)志支持多種語(yǔ)法:

語(yǔ)法說(shuō)明-flagbool類型標(biāo)志可以使用,表示參數(shù)值為true。flag支持兩個(gè)-字符,與-flag等價(jià)。-flag=x所有類型通用,為標(biāo)志flag傳遞參數(shù)值x。-flagx作用等價(jià)于-flag=x,但是僅限非bool類型標(biāo)志使用,假如這樣使用cmd-x*,其中*是Unixshell通配符,如果存在名為0、false等文件,則參數(shù)值結(jié)果會(huì)發(fā)生變化。

flag解析參數(shù)時(shí)會(huì)在第一個(gè)非標(biāo)志參數(shù)之前(單獨(dú)的一個(gè)-字符也是非標(biāo)志參數(shù))或終止符--之后停止。

2.源碼解讀

注意:本文以Go1.19.4源碼為例,其他版本可能存在差異。

熟悉了flag包的基本使用,接下來(lái)我們就要深入到flag的源碼,來(lái)探究其內(nèi)部是如何實(shí)現(xiàn)。

閱讀flag包的源碼,我們可以從使用flag包的流程來(lái)入手。

2.1定義標(biāo)志

在main函數(shù)中,我們首先通過(guò)如下代碼定義了一個(gè)標(biāo)志-n。

varnFlag=flag.Int("n",1234,"helpmessageforflagn")

flag.Int函數(shù)定義如下:

funcInt(namestring,valueint,usagestring)*int{

returnCommandLine.Int(name,value,usage)

}

可以發(fā)現(xiàn),flag.Int函數(shù)調(diào)用并返回了CommandLine對(duì)象的Int方法,并將參數(shù)原樣傳遞進(jìn)去。

來(lái)看看CommandLine是個(gè)什么:

varCommandLine=NewFlagSet(os.Args[0],ExitOnError)

funcNewFlagSet(namestring,errorHandlingErrorHandling)*FlagSet{

f:=FlagSet{

name:name,

errorHandling:errorHandling,

f.Usage=f.defaultUsage

returnf

}

CommandLine是使用NewFlagSet創(chuàng)建的FlagSet結(jié)構(gòu)體指針,在構(gòu)造FlagSet對(duì)象時(shí),需要兩個(gè)參數(shù)os.Args[0]和ExitOnError。

我們知道os.Args存儲(chǔ)了程序執(zhí)行時(shí)指定的所有命令行參數(shù),os.Args[0]就是當(dāng)前命令行程序的名稱,ExitOnError是一個(gè)常量,用來(lái)標(biāo)記在出現(xiàn)error時(shí)應(yīng)該如何做,ExitOnError表示在遇到error時(shí)退出程序。

來(lái)看下FlagSet是如何定義:

typeFlagSetstruct{

Usagefunc()

namestring

parsedbool

actualmap[string]*Flag

formalmap[string]*Flag

args[]string//argumentsafterflags

errorHandlingErrorHandling

outputio.Writer//nilmeansstderr;useOutput()accessor

}

Usage字段是一個(gè)函數(shù),根據(jù)名字大概能夠猜測(cè)出,這個(gè)函數(shù)會(huì)在指定--help/-h參數(shù)查看命令行程序使用幫助時(shí)被調(diào)用。

parsed用來(lái)標(biāo)記是否調(diào)用過(guò)flag.Parse()。

actual和formal分別用來(lái)存儲(chǔ)從命令行解析的標(biāo)志參數(shù)和在程序中指定的默認(rèn)標(biāo)志參數(shù)。它們都使用map來(lái)存儲(chǔ)Flag類型的指針,F(xiàn)lagSet可以看作是Flag結(jié)構(gòu)體的「集合」。

args用來(lái)保存處理完標(biāo)志后剩余的參數(shù)列表。

errorHandling標(biāo)記在出現(xiàn)error時(shí)應(yīng)該如何做。

output用來(lái)設(shè)置輸出位置,這可以改變--help/-h時(shí)展示幫助信息的輸出位置。

現(xiàn)在來(lái)看下Flag的定義:

typeFlagstruct{

Namestring//標(biāo)志名稱

Usagestring//幫助信息

ValueValue//標(biāo)志所對(duì)應(yīng)的命令行參數(shù)值

DefValuestring//用來(lái)記錄字符串類型的默認(rèn)值,它不會(huì)被改變

}

Flag用來(lái)記錄一個(gè)命令行參數(shù),里面存儲(chǔ)了一個(gè)標(biāo)志所有信息。

可以說(shuō)Flag和FlagSet兩個(gè)結(jié)構(gòu)體就是flag包的核心,所有功能都是圍繞這兩個(gè)結(jié)構(gòu)體設(shè)計(jì)的。

標(biāo)志所對(duì)應(yīng)的命令行參數(shù)值為flag.Value接口類型,在前文中已經(jīng)見(jiàn)過(guò)了,定義如下:

typeValueinterface{

String()string

Set(string)error

}

之所以使用接口,是為了能夠存儲(chǔ)任何類型的值,除了flag包默認(rèn)支持的內(nèi)置類型,用戶也可以定義自己的類型,只要實(shí)現(xiàn)了Value接口即可。

如我們?cè)谇拔氖纠绦蛑卸x的flagVal類型。

現(xiàn)在CommandLine的定義以及內(nèi)部實(shí)現(xiàn)我們都看過(guò)了,是時(shí)候回過(guò)頭來(lái)看一看CommandLine對(duì)象的Int方法了:

func(f*FlagSet)Int(namestring,valueint,usagestring)*int{

p:=new(int)

f.IntVar(p,name,value,usage)

returnp

}

Int方法內(nèi)部調(diào)用了f.IntVar()方法,定義如下:

func(f*FlagSet)IntVar(p*int,namestring,valueint,usagestring){

f.Var(newIntValue(value,p),name,usage)

}

IntVar方法又調(diào)用了f.Var()方法。

Var方法第一個(gè)參數(shù)為newIntValue(value,p),我們來(lái)看看newIntValue函數(shù)是如何定義的:

typeintValueint

funcnewIntValue(valint,p*int)*intValue{

*p=val

return(*intValue)(p)

func(i*intValue)Set(sstring)error{

v,err:=strconv.ParseInt(s,0,strconv.IntSize)

iferr!=nil{

err=numError(err)

*i=intValue(v)

returnerr

func(i*intValue)Get()any{returnint(*i)}

func(i*intValue)String()string{returnstrconv.Itoa(int(*i))}

newIntValue是一個(gè)構(gòu)造函數(shù),用來(lái)創(chuàng)建一個(gè)intValue類型的指針,intValue底層類型實(shí)際上是int。

定義intValue類型的目的就是為了實(shí)現(xiàn)flag.Value接口。

再來(lái)看下Var方法如何定義:

func(f*FlagSet)Var(valueValue,namestring,usagestring){

//Flagmustnotbegin"-"orcontain"=".

ifstrings.HasPrefix(name,"-"){

panic(f.sprintf("flag%qbeginswith-",name))

}elseifstrings.Contains(name,"="){

panic(f.sprintf("flag%qcontains=",name))

//Rememberthedefaultvalueasastring;itwon'tchange.

flag:=Flag{name,usage,value,value.String()}

_,alreadythere:=f.formal[name]

ifalreadythere{

varmsgstring

if==""{

msg=f.sprintf("flagredefined:%s",name)

}else{

msg=f.sprintf("%sflagredefined:%s",,name)

panic(msg)//Happensonlyifflagsaredeclaredwithidenticalnames

iff.formal==nil{

f.formal=make(map[string]*Flag)

f.formal[name]=flag

}

name參數(shù)即為標(biāo)志名,在Var方法內(nèi)部,首先對(duì)標(biāo)志名的合法性進(jìn)行了校驗(yàn),不能以-開(kāi)頭且不包含=。

接著,根據(jù)參數(shù)創(chuàng)建了一個(gè)Flag類型,并且校驗(yàn)了標(biāo)志是否被重復(fù)定義。

最后將Flag保存在formal屬性中。

到這里,整個(gè)函數(shù)調(diào)用關(guān)系就結(jié)束了,我們來(lái)梳理一下代碼執(zhí)行流程:

flag.Int-CommandLine.Int-CommandLine.IntVar-CommandLine.Var。

經(jīng)過(guò)這個(gè)調(diào)用過(guò)程,我們就得到了一個(gè)Flag對(duì)象,其名稱為n、默認(rèn)參數(shù)值為1234、值的類型為intValue、幫助信息為helpmessageforflagn。并將這個(gè)Flag對(duì)象保存在了CommandLine這個(gè)類型為FlagSet的結(jié)構(gòu)體指針對(duì)象的formal屬性中。

我們?cè)谑纠绦蛑羞€使用了另外兩種方式定義標(biāo)志。

使用flag.IntVar(flagvar,flagvar,1234,helpmessageforflagvar)定義標(biāo)志-flagvar。

flag.IntVar定義如下:

funcIntVar(p*int,namestring,valueint,usagestring){

CommandLine.Var(newIntValue(value,p),name,usage)

}

可以發(fā)現(xiàn),flag.IntVar函數(shù)內(nèi)部沒(méi)有調(diào)用CommandLine.Int和CommandLine.IntVar的過(guò)程,而是直接調(diào)用CommandLine.Var。

另外,我們還使用flag.Var(val,val,helpmessageforval)定義了-val標(biāo)志。

flag.Var定義如下:

funcVar(valueValue,namestring,usagestring){

CommandLine.Var(value,name,usage)

}

flag.Var函數(shù)內(nèi)部同樣直接調(diào)用了CommandLine.Var,并且由于參數(shù)value已經(jīng)是Value接口類型,可以無(wú)需調(diào)用newIntValue這類構(gòu)造函數(shù)將Go內(nèi)置類型轉(zhuǎn)為Value類型,直接傳遞參數(shù)即可。

2.2解析標(biāo)志參數(shù)

命令行參數(shù)定義完成了,終于到了解析部分,可以使用flag.Parse()解析命令行參數(shù)。

flag.Parse函數(shù)代碼如下:

funcParse(){

CommandLine.Parse(os.Args[1:])

}

內(nèi)部同樣是調(diào)用CommandLine對(duì)象對(duì)應(yīng)的方法,并且將除程序名稱以外的命令行參數(shù)都傳遞到Parse方法中,Parse方法定義如下:

func(f*FlagSet)Parse(arguments[]string)error{

f.parsed=true

f.args=arguments

for{

seen,err:=f.parseOne()

ifseen{

continue

iferr==nil{

break

switchf.errorHandling{

caseContinueOnError:

returnerr

caseExitOnError:

iferr==ErrHelp{

os.Exit(0)

os.Exit(2)

casePanicOnError:

panic(err)

returnnil

}

首先將f.parsed標(biāo)記為true,在調(diào)用f.Parsed()方法時(shí)會(huì)被返回:

func(f*FlagSet)Parsed()bool{

returnf.parsed

}

接著又將arguments保存在f.args屬性中。

然后就是循環(huán)解析命令行參數(shù)的過(guò)程,每調(diào)用一次f.parseOne()解析一個(gè)標(biāo)志,直到解析完成或遇到error退出程序。

parseOne方法實(shí)現(xiàn)如下:

func(f*FlagSet)parseOne()(bool,error){

iflen(f.args)==0{

returnfalse,nil

s:=f.args[0]

iflen(s)2||s[0]!='-'{

returnfalse,nil

numMinuses:=1

ifs[1]=='-'{

numMinuses++

iflen(s)==2{//"--"terminatestheflags

f.args=f.args[1:]

returnfalse,nil

name:=s[numMinuses:]

iflen(name)==0||name[0]=='-'||name[0]=='='{

returnfalse,f.failf("badflagsyntax:%s",s)

//it'saflag.doesithaveanargument

f.args=f.args[1:]

hasValue:=false

value:=""

fori:=1;ilen(name);i++{//equalscannotbefirst

ifname[i]=='='{

value=name[i+1:]

hasValue=true

name=name[0:i]

break

m:=f.formal

flag,alreadythere:=m[name]//BUG

if!alreadythere{

ifname=="help"||name=="h"{//specialcasefornicehelpmessage.

f.usage()

returnfalse,ErrHelp

returnfalse,f.failf("flagprovidedbutnotdefined:-%s",name)

iffv,ok:=flag.Value.(boolFlag);okfv.IsBoolFlag(){//specialcase:doesn'tneedanarg

ifhasValue{

iferr:=fv.Set(value);err!=nil{

returnfalse,f.failf("invalidbooleanvalue%qfor-%s:%v",value,name,err)

}else{

iferr:=fv.Set("true");err!=nil{

returnfalse,f.failf("invalidbooleanflag%s:%v",name,err)

}else{

//Itmusthaveavalue,whichmightbethenextargument.

if!hasValuelen(f.args)0{

//valueisthenextarg

hasValue=true

value,f.args=f.args[0],f.args[1:]

if!hasValue{

returnfalse,f.failf("flagneedsanargument:-%s",name)

iferr:=flag.Value.Set(value);err!=nil{

returnfalse,f.failf("invalidvalue%qforflag-%s:%v",value,name,err)

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論