08.2 flag包

标识是特殊格式的字符串,可以传入程序以控制其行为。如果希望支持多个标识,那么自己单独处理标识可能会很困难。因此,如果你正在开发Unix系统命令行实用程序,你会发现flag包非常有趣和有用。在它的其他特性中,flag包对命令行参数和选项的顺序没有任何假设,并且在命令行实用程序执行过程中出现错误时,它会打印有用的信息。

flag包的最大好处是,它是Go标准库的一部分,意味着它经过了大量的测试和调试。

我将介绍两个使用flag包的Go程序:一个简单的和一个复杂的。

第一个程序,称为simpleFlag.go,分为四部分。simpleFlag.go程序识别两个命令行选项:第一个是Boolean选项,第二个是整数值。

simpleFlag.go的第一部分如下:

import (
    "flag"
    "fmt"
)

simpleFlag.go的第二部分如下:

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

flag.Bool("k", true, "k")语句定义了名为kBoolean型的命令行选项,默认值为true。语句的最后一个参数是将与程序的用法信息一起显示的字符串。类似,flag.Int()函数的作用是增加对整数命令行选项的支持。

通常,在定义了命令行选项后,需要调用flag.Parse()

simpleFlag.go的第三部分程序如下:

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

在前面的Go代码中,你看到了如何获取选项的值。好处是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中遇到错误,则会得到如下错误信息:

$ go run simpleFlag.go -O=notAnInteger
invalid value "notAnInteger" for flag -O: strconv.ParseInt: parsing "notAnInteger": invalid syntax
Usage of /var/folders/sk/ltk8cnw501zdtr2hxcj5sv2m0000gn/T/go-build020625525/command-line arguments/_obj/exe/simpleFlag:
  -O int
        O (default 1)
  -k    k (default true)
exit status 2

注意:当程序的命令行选项中出现错误时,将自动打印常规用法信息。

现在该是展示一个使用flag包更实际、更高级的程序了。程序funWithFlag.go分为五部分。funWithFlag.go识别各种选项,包括一个由逗号分隔的多个值的选项。此外,它还将说明如何访问位于可执行文件末尾且不属于任何选项的命令行参数。

funWithFlag.go中使用的flag.Var()函数创建一个满足flag.Value接口的任意类型的标识,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("Remaing command-line arugments:")
    for index, val := range flag.Args() {
        fmt.Println(index, ":", val)
    }
}

flag.Args() 切片保留命令行参数,而 manyNames 变量保留来自 flag.Var() 命令行选项的值。

执行 funWithFlag.go 产生如下输出:

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

Last updated