当前位置: 首页 > 科技观察

命令行参数的解析:Flag库详解

时间:2023-03-14 00:47:42 科技观察

Golang程序中处理命令行参数的方式有很多种。简单情况下可以直接使用os.Argspackagemainimport("fmt""os")funcmain(){iflen(os.Args)>0{forindex,arg:=rangeos.Args{fmt.Printf("args[%d]=%v\n",index,arg)}}}尝试运行一下,第一个参数是可执行文件的路径。$gorundemo.gohelloworldhellogolangargs[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demoargs[1]=helloargs[2]=worldargs[3]=helloargs[4]=上面的golang可以看到,os.Args只能处理简单的参数,并且对参数的位置有严格的要求。对于一些比较复杂的场景,需要自己定义解析规则,非常麻烦。如果真的遇到所谓的复杂场景,也可以使用Golang的标准库flag包来处理命令行参数。本文将介绍Golang标准库中flag包的用法。1.参数的类型根据参数是否为布尔型,可以分为两种类型:布尔型参数:如--debug,后面不需要连接具体的值,如果指定,则为True,如果未指定,它将是False。Non-BooleanparametersnotBoolean类型参数:非布尔类型,可以是int、string等类型,比如--namejack,后面跟具体的参数值。根据参数名的长度,还可以分为:长参数:如--namejack是一个长参数,参数名前有两个-短参数:通常是一两个字母(缩写对应长参数),比如-n,参数名前只有一个-2。入门的例子,我先用字符串类型参数的例子,抛砖引玉packagemainimport("flag""fmt")funcmain(){varnamestringflag.StringVar(&name,"name","jack","yourname")flag.Parse()//解析参数fmt.Println(name)}flag.StringVar定义了一个字符串参数,它接受几个参数。第一个参数:接收到值后,存放到哪个变量中,需要是指针。第二个参数:命令行中使用的参数名称,例如--namenameinjack第三个参数:如果命令行中没有指定参数值,则默认为jack第四个参数:记录用途或者这个参数的含义运行上面的程序,输出如下$gorundemo.gojack$gorundemo.go--namewangbmwangbm3。改进一下如果你的程序只接受几个参数,按上面这样写是没有问题的。但是一旦参数数量增多,main函数中就会堆积大量的参数解析代码,影响代码的可读性和美观性。建议将参数解析代码放在init函数中,init函数会在main函数之前执行。packagemainimport("flag""fmt")varnamestringfuncinit(){flag.StringVar(&name,"name","jack","yourname")}funcmain(){flag.Parse()fmt.Println(name)}4.参数类型当你在命令行指定一个参数时,Go如何解析这个参数并将其转换成一个类型需要预先定义。不同的参数对应flag中不同的方法。让我们谈谈如何定义不同的参数类型。Boolean实现效果:不指定--debug时,debug默认值为false。一旦您指定--debug,debug就会被分配一个true值。vardebugboolfuncinit(){flag.BoolVar(&debug,"debug",false,"是否开启DEBUG模式")}funcmain(){flag.Parse()fmt.Println(debug)}运行后执行结果如下$gorunmain.gofalse$gorunmain.go--debugtrueNumeric定义一个age参数,如果不指定,默认为18varageintfuncinit(){flag.IntVar(&age,"age",18,"yourage")}funcmain(){flag.Parse()fmt.Println(age)}运行后,执行结果如下$gorunmain.go18$gorunmain.go--age2020int64、uint、float64类型分别对应Int64Var、UintVar、Float64Var方法。同理,此处不再赘述。String定义了一个name参数,如果不指定,默认为jackvarnamestringfuncinit(){flag.StringVar(&name,"name","jack","yourname")}funcmain(){flag.Parse()fmt.Println(name)}运行后,执行结果如下interval",1*time.Second,"cycleinterval")}funcmain(){flag.Parse()fmt.Println(interval)}验证效果如下$gorunmain.go1s$gorunmain.go--interval2s2s5.自定义类型标志包支持的类型有Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。这些类型的参数被封装到它们对应的后端类型中。比如Int类型的参数被封装为intValue,String类型的参数被封装为stringValue。这些后端类型都实现了flag.Value接口,因此命令行参数可以抽象为Flag类型的实例。下面是Value接口与Flag类型的代码:不同种类。defValuestring//defaultvalue(astext);forusagemessage}funcVar(valueValue,namestring,usagestring){CommandLine.Var(value,name,usage)}如果要实现自定义类型的参数,其实只要第一个参数Var函数的对象被实现flag.Value接口可以是类型sliceValue[]stringfuncnewSliceValue(vals[]string,p*[]string)*sliceValue{*p=valsreturn(*sliceValue)(p)}func(s*sliceValue)Set(valstring)error{//如何解析参数值*s=sliceValue(strings.Split(val,","))returnnil}func(s*sliceValue)String()string{returnstrings.Join([]string(*s),",")}比如我要实现如下效果。传入的参数是逗号分隔的字符串,解析时会把flag转换成slice。$gorundemo.go-members"Jack,Tom"[JackTom]然后我可以写这样的代码)(p)}func(s*sliceValue)Set(valstring)error{//如何解析参数值*s=sliceValue(strings.Split(val,","))returnnil}func(s*sliceValue)String()string{returnstrings.Join([]string(*s),",")}funcinit(){flag.Var(newSliceValue([]string{},&members),"members","memberlist")}funcmain(){flag.Parse()fmt.Println(members)}可能有朋友对这行代码(*sliceValue)(p)有疑惑,这是什么意思?关于这个,其实之前【2.9详解:静态类型和动态类型】之前已经说过了,忘记了可以去复习一下。6.长短选项标志包,在使用中,长短选项没有区别,可以看下面的例子,"明哥","你的名字")}funcmain(){flag.Parse()fmt.Println(name)}通过指定如下参数形式$gorunmain.goMingge$gorunmain.go--namejackjack$gorunmain。go-namejackjack一和二的执行结果是一样的。那么再添加一个呢?最后报错。注意最多只能指定两个-$gorunmain.go---namejackbadflagsyntax:---nameUsageof/tmp/go-build245956022/b001/exe/main:-namestringyourname(default"Mingge")exitstatus27.总结flag在大多数场景下已经足够了,但是如果你想支持更多的命令输入格式,flag可能不是最好的选择。对于那些标准库无法解决的场景,往往有相应的围棋爱好者提供第三方解决方案。据我所知,cobra是一个非常好的库。本文转载自微信公众号“Go编程时间”,可通过以下二维码关注。转载本文请联系围棋编程时间公众号。