08.2 flag 包

标签(flag)是传入程序来控制行为的特殊格式化字符串。对于支持多标签的程序,自己处理这些标签可能有点麻烦。因此,如果您在开发 Unix 系统命令行工具的话,您会发现 flag 包是很有趣而且十分有用。

flag 包对命令行参数和选项的顺序没有要求,当执行命令行工具错误时会输出帮助信息。

flag 包最大的优势是它是 Go 的标准库,这意味着它经过了大量的测试和调试。

我会展示两个使用 flag 包的 Go 程序:一个简单的和一个稍复杂的。第一个是 simpleFlag.go,分为四部分。simpleFlag.go 有两个命令行选项:第一个是布尔选项,第二个是整数值选项。

simpleFlag.go 的第一部分代码如下:

package main

import (
    "flag"
    "fmt"
)

simpleFlag.go 的第二部分代码如下:

func main() {
    minusK := flag.Bool("k", true, "k flag")
    minusO := flag.Int("O", 1, "O")
    flag.Parse()

flag.Bool("k", true, "k") 语句定义了一个布尔值命令选项,命名为 k,默认值为 true。最后一个参数是选项的用法,在输出程序的使用信息时显示的。类似的,flag.Int() 函数用来添加整数命令行选项。

在定义好需要的命令行选项后,您必须调用 flag.Parse() 才能使用。

simpleFlag.go 的第三部分代码如下:

    valueK := *minusK
    valueO := *minusO
    valueO++

上面的代码展示了如何获取您定义的选项值。在这里 flag 包自动把 flag.Int() 标签对应的输入值转为整数值,所以您不必自己进行转化。此外,flag 包确保分配了一个可接受的整数值。

simpleFlag.go 的其余代码如下:

    fmt.Println("-k:", valueK)
    fmt.Println("-O:", valueO)
}

现在,您现在就可以使用获取到的参数了。

执行 simpleFlag.go 输出如下:

$ go run simpleFlag.go -O 100
-k: true
-O: 101
$ go run simpleFlag.go -O=100
-k: true -O: 101
$ go run simpleFlag.go -O=100 -k
-k: true
-O: 101
$ go run simpleFlag.go -O=100 -k false
-k: true
-O: 101
$ go run simpleFlag.go -O=100 -k=false
-k: false
-O: 101

如果执行 simpleFlag.go 发生错误,flag 包会输出如下的错误信息:

$ go run simpleFlag.go -O=notAnInteger
invalid value "notAnInteger" for flag -O: parse error
Usage of /var/folders/sk/ltk8cnw50lzdtr2hxcj5sv2m0000gn/T/go-
build593534621/b001/exe/simpleFlag:
    -O int
          O (default 1)
    -k    flag (default true)
exit status 2

注意,这个方便的使用信息是当您输入了错误的命令行选项时程序自动输出的。

现在是时候介绍一个更实用、更高级的使用 flag 包的程序了 —— funWithFlag.go,包含五个部分。 funWithFlag.go 可以识别多种类型的命令行选项,其中一个选项可以通过逗号分隔多个值。此外,它展示了如何访问位于执行程序末尾但不属于任何选项的命令行参数。

funWithFlag.go 中的 flag.Var() 函数创建了一个接受任何类型的标签,只要实现了 flag.Value 接口即可,该接口定义如下:

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

funWithFlag.go 的第一部分如下:

package main

import (
    "flag"
    "fmt"
    "strings"
)

type NamesFlag struct {
    Names []string
}

后面我们很快会用到 NamesFlag 来作为 flag.Value 接口

funWithFlag.go 的第二部分如下:

func (s *NamesFlag) GetNames() []string {
    return s.Names
}

func (s *NamesFlag) String() string {
    return fmt.Sprint(s.Names)
}

funWithFlag.go 的第三部分如下:

func (s *NamesFlag) Set(v string) error {
    if len(s.Names) > 0 {
        return fmt.Errorf("Cannot use names flag more than once!")
    }

    names := strings.Split(v, ",")
    for _, item := range names {
        s.Names = append(s.Names, item)
    }
    return nil
}

Set() 方法首先确保相关命令行选项没有被设置,之后获取输入并使用 strings.Split() 函数来分隔参数,最后将参数被保存在 NamesFlag 结构的 Names 字段中。

funWithFlag.go 的第四部分如下:

func main() {
    var manyNames NamesFlag
    minusK := flag.Int("k", 0, "An int")
    minusO := flag.String("o", "Mihalis", "The name")
    flag.Var(&manyNames, "names", "Comma-separated list")

    flag.Parse()
    fmt.Println("-k:", *minusK)
    fmt.Println("-o:", *minusO)

funWithFlag.go 的最后一部分如下:

    for i, item := range manyNames.GetNames() {
        fmt.Println(i, item)
    }

    fmt.Println("Remaining command line arguments:")
    for index, val := range flag.Args() {
        fmt.Println(index, ":", val)
    }
}

flag.Args() 切片保留剩下的命令行参数,manyNames 变量保留 flag.Var() 命令行参数中的值。

执行 funWithFlag.go 得到如下输出:

$ go run funWithFlag.go -names=Mihalis,Jim,Athina 1 two Three
-k: 0
-o: Mihalis
0 Mihalis
1 Jim
2 Athina
Remaining command line arguments:
0 : 1
1 : two
2 : Three
$ go run funWithFlag.go -Invalid=Marietta 1 two Three
flag provided but not defined: -Invalid U
sage of funWithFlag:
  -k int
        An int
  -names value
        Comma-separated list
  -o string
        The name (default "Mihalis")
exit status 2
$ go run funWithFlag.go -names=Marietta -names=Mihalis
invalid value "Mihalis" for flag -names: Cannot use names flag more than once! Usage of funWithFlag:
  -k int
        An int
  -names value
        Comma-separated list
  -o string
        The name (default "Mihalis")
exit status 2

除非要开发的是一个不需要选项的命令行小工具,否则您很可能需要使用 flag 包来处理您的程序的命令行选项。

Last updated