如何处理命令行参数?介绍 Golang 标准库中 flag 包的用法

时间:2021-01-13 08:07:05 来源: Go编程时光公众号


在 Golang 程序中有很多种方法来处理命令行参数。

简单的情况下可以不使用任何库,直接使用 os.Args

packagemain

import(

"fmt"

"os"

)

funcmain(){

iflen(os.Args)>0{

forindex,arg:=rangeos.Args{

fmt.Printf("args[%d]=%v\n",index,arg)

}

}

}

试着运行一下,第一个参数是执行文件的路径。

$gorundemo.gohelloworldhellogolang

args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo

args[1]=hello

args[2]=world

args[3]=hello

args[4]=golang

从上面你可以看到,os.Args 只能处理简单的参数,而且对于参数的位置有严格的要求。对于一些比较复杂的场景,就需要你自己定义解析规则,非常麻烦。

如果真的遇上了所谓的复杂场景,那么还可以使用 Golang 的标准库 flag 包来处理命令行参数。

本文将介绍 Golang 标准库中 flag 包的用法。

1. 参数种类

根据参数是否为布尔型,可以分为两种:

布尔型参数:如 --debug,后面不用再接具体的值,指定就为 True,不指定就为 False非布尔型参数

非布尔型参数:非布尔型,有可能是int,string 等其他类型,如 --name jack ,后面可以接具体的参数值

根据参数名的长短,还可以分为:

长参数:比如 --name jack 就是一个长参数,参数名前有两个 -

短参数:通常为一个或两个字母(是对应长参数的简写),比如 -n ,参数名前只有一个 -

2. 入门示例

我先用一个字符串类型的参数的示例,抛砖引玉

packagemain

import(

"flag"

"fmt"

)

funcmain(){

varnamestring

flag.StringVar(&name,"name","jack","yourname")

flag.Parse()//解析参数

fmt.Println(name)

}

flag.StringVar 定义了一个字符串参数,它接收几个参数

第一个参数 :接收值后,存放在哪个变量里,需为指针

第二个参数 :在命令行中使用的参数名,比如 --name jack 里的 name

第三个参数 :若命令行中未指定该参数值,那么默认值为 jack

第四个参数:记录这个参数的用途或意义

运行以上程序,输出如下

$gorundemo.go

jack

$gorundemo.go--namewangbm

wangbm

3. 改进一下

如果你的程序只接收很少的几个参数时,上面那样写也没有什么问题。

但一旦参数数量多了以后,一大堆参数解析的代码堆积在 main 函数里,影响代码的可读性、美观性。

建议将参数解析的代码放入 init 函数中,init 函数会先于 main 函数执行。

packagemain

import(

"flag"

"fmt"

)

varnamestring

funcinit(){

flag.StringVar(&name,"name","jack","yourname")

}

funcmain(){

flag.Parse()

fmt.Println(name)

}

4. 参数类型

当你在命令行中指定了参数,Go 如何解析这个参数,转化成何种类型,是需要你事先定义的。

不同的参数,对应着 flag 中不同的方法。

下面分别讲讲不同的参数类型,都该如何定义。

布尔型

实现效果:当不指定 --debug 时,debug 的默认值为 false,你一指定 --debug,debug 为赋值为 true。

vardebugbool

funcinit(){

flag.BoolVar(&debug,"debug",false,"是否开启DEBUG模式")

}

funcmain(){

flag.Parse()

fmt.Println(debug)

}

运行后,执行结果如下

$gorunmain.go

false

$gorunmain.go--debug

true

数值型

定义一个 age 参数,不指定默认为 18

varageint

funcinit(){

flag.IntVar(&age,"age",18,"你的年龄")

}

funcmain(){

flag.Parse()

fmt.Println(age)

}

运行后,执行结果如下

$gorunmain.go

18

$gorunmain.go--age20

20

int64、 uint 和 float64 类型分别对应 Int64Var 、 UintVar、Float64Var 方法,也是同理,不再赘述。

字符串

定义一个 name参数,不指定默认为 jack

varnamestring

funcinit(){

flag.StringVar(&name,"name","jack","你的名字")

}

funcmain(){

flag.Parse()

fmt.Println(name)

}

运行后,执行结果如下

$gorunmain.go

jack

$gorunmain.go--namewangbm

wangbm

时间类型

定义一个 interval 参数,不指定默认为 1s

varintervaltime.Duration

funcinit(){

flag.DurationVar(&interval,"interval",1*time.Second,"循环间隔")

}

funcmain(){

flag.Parse()

fmt.Println(interval)

}

验证效果如下

$gorunmain.go

1s

$gorunmain.go--interval2s

2s

5. 自定义类型

flag 包支持的类型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。

这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 intValue,String 类型的参数被封装为 stringValue。

这些后端的类型都实现了 flag.Value 接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value 接口和 Flag 类型的代码:

typeValueinterface{

String()string

Set(string)error

}

//Flag类型

typeFlagstruct{

Namestring//nameasitappearsoncommandline

Usagestring//helpmessage

ValueValue//valueasset是个interface,因此可以是不同类型的实例。

DefValuestring//defaultvalue(astext);forusagemessage

}

funcVar(valueValue,namestring,usagestring){

CommandLine.Var(value,name,usage)

}

想要实现自定义类型的参数,其实只要 Var 函数的第一个参数对象实现 flag.Value接口即可

typesliceValue[]string

funcnewSliceValue(vals[]string,p*[]string)*sliceValue{

*p=vals

return(*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]

那我可以这样子编写代码

varmembers[]string

typesliceValue[]string

funcnewSliceValue(vals[]string,p*[]string)*sliceValue{

*p=vals

return(*sliceValue)(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","会员列表")

}

funcmain(){

flag.Parse()

fmt.Println(members)

}

有的朋友 可能会对 (*sliceValue)(p) 这行代码有所疑问,这是什么意思呢?

关于这个,其实之前在 【2.9 详细图解:静态类型与动态类型】有讲过,忘记了可以前往复习。

6. 长短选项

flag 包,在使用上,其实并没有没有长短选项之别,你可以看下面这个例子

packagemain

import(

"flag"

"fmt"

)

varnamestring

funcinit(){

flag.StringVar(&name,"name","明哥","你的名字")

}

funcmain(){

flag.Parse()

fmt.Println(name)

}

通过指定如下几种参数形式

$gorunmain.go

明哥

$gorunmain.go--namejack

jack

$gorunmain.go-namejack

jack

一个 - 和两个 - 执行结果是相同的。

那么再加一个呢?

终于报错了。说明最多只能指定两个 -

$gorunmain.go---namejack

badflagsyntax:---name

Usageof/tmp/go-build245956022/b001/exe/main:

-namestring

你的名字(default"明哥")

exitstatus2

7. 总结一下

flag 在绝大多数场景下,它是够用的,但如果要支持更多的命令传入格式,flag 可能并不是最好的选择。

那些在标准库不能解决的场景,往往会有相应的Go爱好者提供第三方解决方案。我所了解到的 cobra 就是一个非常不错的库。

关键词:Golang flag

关于我们 加入我们 广告服务 网站地图

All Rights Reserved, Copyright 2004-2020 www.ctocio.com.cn

如有意见请与我们联系 邮箱:5 53 13 8 [email protected]

豫ICP备20005723号    IT专家网 版权所有