命令行参数解析工具:Pflag 使用介绍

​ Go 服务开发中,经常需要给开发的组件加上各种启动参数来配置服务进程,影响服务的行为。像 kube-apiserver 就有多达 200 多个启动参数,而且这些参数的类型各不相同(例如:string、int、ip 类型等),使用方式也不相同(例如:需要支持 -- 长选项,- 短选项等),所以我们需要一个强大的命令行参数解析工具。

​ 虽然 Go 源码中提供了一个标准库 Flag 包,用来对命令行参数进行解析,但在大型项目中应用更广泛的是另外一个包:Pflag。Pflag 提供了很多强大的特性,非常适合用来构建大型项目,一些耳熟能详的开源项目都是用 Pflag 来进行命令行参数解析的,例如:Kubernetes、Istio、Helm、Docker、Etcd 等。

1、Pflag 包 Flag 定义

学习地址:https://github.com/spf13/pflag
Pflag 可以对命令行参数进行处理,一个命令行参数在 Pflag 包中会解析为一个 Flag 类型的变量。Flag 是一个结构体,定义如下:

type Flag struct { 
    Name string // flag长选项的名称 
    Shorthand string // flag短选项的名称,一个缩写的字符 
    Usage string // flag的使用文本 
    Value Value // flag的值 
    DefValue string // flag的默认值 
    Changed bool // 记录flag的值是否有被设置过 
    NoOptDefVal string // 当flag出现在命令行,但是没有指定选项值时的默认值 
    Deprecated string // 记录该flag是否被放弃 
    Hidden bool // 如果值为true,则从help/usage输出信息中隐藏该flag 
    ShorthandDeprecated string // 如果flag的短选项被废弃,当使用flag的短选项时打印该信息 
    Annotations map[string][]string // 给flag设置注解
}

Flag 的值是一个 Value 类型的接口,Value 定义如下:

type Value interface { 
    String() string // 将flag类型的值转换为string类型的值,并返回string的内容 
    Set(string) error // 将string类型的值转换为flag类型的值,转换失败报错 
    Type() string // 返回flag的类型,例如:string、int、ip等
}

通过将 Flag 的值抽象成一个 interface 接口,我们就可以自定义 Flag 的类型了。只要实现了 Value 接口的结构体,就是一个新类型。

2、Pflag 包 FlagSet 定义

Pflag 除了支持单个的 Flag 之外,还支持 FlagSet。FlagSet 是一些预先定义好的 Flag 的集合,几乎所有的 Pflag 操作,都需要借助 FlagSet 提供的方法来完成。在实际开发中,我们可以使用两种方法来获取并使用 FlagSet:

  • 方法一,调用 NewFlagSet 创建一个 FlagSet。
  • 方法二,使用 Pflag 包定义的全局 FlagSet:CommandLine。实际上 CommandLine 也是由 NewFlagSet 函数创建的。

第一种方法,自定义 FlagSet。下面是一个自定义 FlagSet 的示例:

var version bool
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
flagSet.BoolVar(&version, "version", true, "Print version information and quit.")
flagSet.Parse(os.Args[1:])
fmt.Println(version)

第二种方法,使用全局 FlagSet。下面是一个使用全局 FlagSet 的示例:

import "github.com/spf13/pflag"
var version bool
pflag.BoolVarP(&version, "version", "v", true, "Print version information and quit.")

这其中,pflag.BoolVarP 函数定义如下:

func BoolVarP(p *bool, name, shorthand string, value bool, usage string) { 
    flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage)
    flag.NoOptDefVal = "true"
}

可以看到 pflag.BoolVarP 最终调用了 CommandLine,CommandLine 是一个包级别的变量,定义为:

// CommandLine is the default set of command-line flags, parsed from os.Args.var 
CommandLine = NewFlagSet(os.Args[0], ExitOnError)

在一些不需要定义子命令的命令行工具中,我们可以直接使用全局的 FlagSet,更加简单方便。

3 、Pflag 使用方法

​ 上面,我们介绍了使用 Pflag 包的两个核心结构体。接下来,我来详细介绍下 Pflag 的常见使用方法。Pflag 有很多强大的功能,我这里介绍 7 个常见的使用方法。
1、支持多种命令行参数定义方式。

Pflag 支持以下 4 种命令行参数定义方式:

  • 支持长选项、默认值和使用文本,并将标志的值存储在指针中。
var name = pflag.String("name", "colin", "Input Your Name")
  • 支持长选项、短选项、默认值和使用文本,并将标志的值存储在指针中。
var name = pflag.StringP("name", "n", "colin", "Input Your Name")
  • 支持长选项、默认值和使用文本,并将标志的值绑定到变量。
var name string
pflag.StringVar(&name, "name", "colin", "Input Your Name")
  • 支持长选项、短选项、默认值和使用文本,并将标志的值绑定到变量。
var name string
pflag.StringVarP(&name, "name", "n","colin", "Input Your Name")

上面的函数命名是有规则的:

  • 函数名带Var说明是将标志的值绑定到变量,否则是将标志的值存储在指针中。
  • 函数名带P说明支持短选项,否则不支持短选项。

2、使用Get获取参数的值。

​ 可以使用Get来获取标志的值,代表 Pflag 所支持的类型。例如:有一个 pflag.FlagSet,带有一个名为 flagname 的 int 类型的标志,可以使用GetInt()来获取 int 值。需要注意 flagname 必须存在且必须是 int,例如:

i, err := flagset.GetInt("flagname")

3、获取非选项参数。

package main
import ( "fmt" "github.com/spf13/pflag")
var ( 
    flagvar = pflag.Int("flagname", 1234, "help message for flagname")
)
func main() { 
    pflag.Parse() 
    fmt.Printf("argument number is: %v\n", pflag.NArg()) 
    fmt.Printf("argument list is: %v\n", pflag.Args()) 
    fmt.Printf("the first argument is: %v\n", pflag.Arg(0))
}

执行上述代码,输出如下:

go run .\pflag_example2.go --flagname 12345  345 567

argument number is: 2
argument list is: [345 567]
the first argument is: 345

​ 在定义完标志之后,可以调用pflag.Parse()来解析定义的标志。解析后,可通过pflag.Args()返回所有的非选项参数,通过pflag.Arg(i)返回第 i 个非选项参数。参数下标 0 到 pflag.NArg() - 1。

4、指定了选项但是没有指定选项值时的默认值。
​ 创建一个 Flag 后,可以为这个 Flag 设置 pflag.NoOptDefVal。如果一个 Flag 具有 NoOptDefVal,并且该 Flag 在命令行上没有设置这个 Flag 的值,则该标志将设置为 NoOptDefVal 指定的值。例如:

var ip = pflag.IntP("flagname", "f", 1234, "help message")
pflag.Lookup("flagname").NoOptDefVal = "4321"

上面的代码会产生结果如下表:

命令行参数 解析结果
--flagname=1357 ip=1357
--flagname ip=4321
[nothing] ip=1234

5、弃用标志或者标志的简写。

​ Pflag 可以弃用标志或者标志的简写。弃用的标志或标志简写在帮助文本中会被隐藏,并在使用不推荐的标志或简写时打印正确的用法提示。例如,弃用名为 logmode 的标志,并告知用户应该使用哪个标志代替:

// deprecate a flag by specifying its name and a usage message
pflag.CommandLine.MarkDeprecated("logmode", "please use --log-mode instead")

这样隐藏了帮助文本中的 logmode,并且当使用 logmode 时,打印了Flag --logmode has been deprecated, please use --log-mode instead。

6、保留名为 port 的标志,但是弃用它的简写形式。

pflag.IntVarP(&port, "port", "P", 3306, "MySQL service host port.")
// deprecate a flag shorthand by specifying its flag name and a usage message
pflag.CommandLine.MarkShorthandDeprecated("port", "please use --port only")

​ 这样隐藏了帮助文本中的简写 P,并且当使用简写 P 时,打印了Flag shorthand -P has been deprecated, please use --port only。usage message 在此处必不可少,并且不应为空。

7、隐藏标志。

​ 可以将 Flag 标记为隐藏的,这意味着它仍将正常运行,但不会显示在 usage/help 文本中。例如:隐藏名为 secretFlag 的标志,只在内部使用,并且不希望它显示在帮助文本或者使用文本中。代码如下:

// hide a flag by specifying its name
pflag.CommandLine.MarkHidden("secretFlag")
posted @ 2022-04-04 20:55  liweiboy  阅读(832)  评论(0编辑  收藏  举报