Go 服务开发中,经常需要给开发的组件加上各种启动参数来配置服务进程,影响服务的行为。像 kube-apiserver 就有多达 200 多个启动参数,而且这些参数的类型各不相同(例如:string、int、ip 类型等),使用方式也不相同(例如:需要支持 -- 长选项,- 短选项等),所以我们需要一个强大的命令行参数解析工具。
虽然 Go 源码中提供了一个标准库 Flag 包,用来对命令行参数进行解析,但在大型项目中应用更广泛的是另外一个包:Pflag。Pflag 提供了很多强大的特性,非常适合用来构建大型项目,一些耳熟能详的开源项目都是用 Pflag 来进行命令行参数解析的,例如:Kubernetes、Istio、Helm、Docker、Etcd 等。
学习地址: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 接口的结构体,就是一个新类型。
Pflag 除了支持单个的 Flag 之外,还支持 FlagSet。FlagSet 是一些预先定义好的 Flag 的集合,几乎所有的 Pflag 操作,都需要借助 FlagSet 提供的方法来完成。在实际开发中,我们可以使用两种方法来获取并使用 FlagSet:
第一种方法,自定义 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,更加简单方便。
上面,我们介绍了使用 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")
上面的函数命名是有规则的:
2、使用Get
可以使用Get
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")