5.3 命令行参数的解析:flag 库详解

image0

参数种类

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

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

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

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

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

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

入门示例

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

package main

import (
    "flag"
    "fmt"
)

func main(){
    var name string
    flag.StringVar(&name, "name", "jack", "your name")

flag.Parse()  // 解析参数
    fmt.Println(name)
}

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

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

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

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

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

运行以上程序,输出如下

$ go run demo.go --name wangbm
wangbm

改进一下

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

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

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

package main

import (
    "flag"
    "fmt"
)

var name string

func init()  {
    flag.StringVar(&name, "name", "jack", "your name")
}

func main(){
    flag.Parse()
    fmt.Println(name)
}

参数类型

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

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

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

布尔型

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

var debug bool

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

func main(){
    flag.Parse()
    fmt.Println(debug)
}

运行后,执行结果如下

$ go run main.go
false
$ go run main.go --debug
true

数值型

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

var age int

func init()  {
    flag.IntVar(&age, "age", 18, "你的年龄")
}

func main(){
    flag.Parse()
    fmt.Println(age)
}

运行后,执行结果如下

$ go run main.go
18
$ go run main.go --age 20
20

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

字符串

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

var name string

func init()  {
    flag.StringVar(&name, "name", "jack", "你的名字")
}

func main(){
    flag.Parse()
    fmt.Println(name)
}

运行后,执行结果如下

$ go run main.go
jack
$ go run main.go --name wangbm
wangbm

时间类型

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

var interval time.Duration

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

func main(){
    flag.Parse()
    fmt.Println(interval)
}

验证效果如下

$ go run main.go
1s
$ go run main.go --interval 2s
2s

自定义类型

另外,还可以创建自定义flag,只要 Var 函数的第一个参数对象实现 flag.Value接口即可

type Value interface {
    String() string
    Set(string) error
}

func Var(value Value, name string, usage string) {
    CommandLine.Var(value, name, usage)
}

假如我想实现这样一个效果

$ go run demo.go -members "Jack,Tom"
[Jack Tom]

我可以这样子编写代码

var members []string
type sliceValue []string


func newSliceValue(vals []string, p *[]string) *sliceValue {
    *p = vals
    return (*sliceValue)(p)
}

func (s *sliceValue) Set(val string) error {
         // 如何解析参数值
    *s = sliceValue(strings.Split(val, ","))
    return nil
}


func (s *sliceValue) String() string {
    return strings.Join([]string(*s), ",")
}

func init()  {
    flag.Var(newSliceValue([]string{}, &members), "members", "会员列表")
}

func main(){
    flag.Parse()
    fmt.Println(members)
}

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

关于这个,其实之前在 【http://golang.iswbm.com/en/latest/c02/c02_09.html#id2】有讲过,忘记了可以前往复习。

长短选项

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

package main

import (
    "flag"
    "fmt"
)

var name string

func init()  {
    flag.StringVar(&name, "name", "明哥", "你的名字")
}

func main(){
    flag.Parse()
    fmt.Println(name)
}

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

$ go run main.go
明哥
$ go run main.go --name jack
jack
$ go run main.go -name jack
jack

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

那么再加一个呢?

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

$ go run main.go ---name jack
bad flag syntax: ---name
Usage of /tmp/go-build245956022/b001/exe/main:
  -name string
        你的名字 (default "明哥")
exit status 2

flag 的函数

Lookup

从众多数参数中查取出 members 的参数值

m := flag.Lookup("members")

go-flags

flag 内置库有一些不足的地方

  • 不显示支持短选项。

  • 选项变量的定义比较繁琐,每个选项都需要根据类型调用对应的TypeTypeVar函数;

  • 默认只支持有限的数据类型,当前只有基本类型bool/int/uint/stringtime.Duration

image1