Go应用构建工具(1)--pflag
也是今天学习的对象。pflag是一个用来替代Go标准库flag包的,兼容flag库,几乎不用更改就可以替换,pflag在大型项目中应用的比较广泛,比较知名的有:K8S,Docker,ETCD等。作者spf13大牛,还有其他好几个强大的开源库,包括配置神器Viper,命令行框架cobra等(这两个接下来都会学习一波)
Go应用构建工具–pflag
1. 概述
Go标准库提供了一个解析命令行的flag库,小型项目使用flag库其实已经足够用了,但在大型项目中应用较多的是另一个开源库:pflag,也是今天学习的对象。
pflag是一个用来替代Go标准库flag包的,兼容flag库,几乎不用更改就可以替换,pflag在大型项目中应用的比较广泛,比较知名的有:K8S,Docker,ETCD等。
pflag包github地址为:https://github.com/spf13/pflag
作者spf13大牛,还有其他好几个强大的开源库,包括配置神器Viper,命令行框架cobra等(这两个接下来都会学习一波)
2. 快速使用
package main
import (
"fmt"
"github.com/spf13/pflag"
)
var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var stringpFlag = pflag.StringP("stringpflag", "s", "stringpflag", "stringp flag usage")
var intFlag int
var boolFlag bool
func init() {
pflag.IntVarP(&intFlag, "intflag", "i", 0, "int flag usage")
pflag.BoolVar(&boolFlag, "boolflag", false, "bool flag usage")
}
func main() {
pflag.Parse()
// flag保存在指针的
fmt.Println("stringflag = ", *stringFlag)
fmt.Println("stringpflag = ", *stringpFlag)
// flag绑定在变量
fmt.Println("intflag = ", intFlag)
fmt.Println("boolflag = ", boolFlag)
// 也可以使用Getxxx方法获取
str, _ := pflag.CommandLine.GetString("stringflag")
fmt.Println("str = ", str)
}
可以先编译程序,然后再运行,这里直接用go run
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go
stringflag = stringflag
stringpflag = stringpflag
intflag = 0
boolflag = false
str = stringflag
在没有传递flag时,输出的都是默认值。
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --stringflag hello -s world -i 1234 --boolflag true
stringflag = hello
stringpflag = world
intflag = 1234
boolflag = true
str = hello
指定flag的值后运行,相应的值都被设置了。
还可以使用-h
打印帮助信息
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -h
Usage of /tmp/go-build2029445686/b001/exe/pflag_fast:
--boolflag bool flag usage
-i, --intflag int int flag usage
--stringflag string string flag usage (default "stringflag")
-s, --stringpflag string stringp flag usage (default "stringpflag")
pflag: help requested
exit status 2
小结
- 使用pflag定义命令行参数有好几种方式,长选项/短选项/保存在指针/绑定到变量…
pflag.Parse()
方法必须放在所有flag都定义后调用,否则flag就无法解析了。
3. PFlag包结构
从上面的快速使用例子里,使用Getxxx方法来获取flag的值:str, _ := pflag.CommandLine.GetString("stringflag")
这里调用了pflag包的CommandLine
,查看源码CommandLine的定义如下:
// CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
实质上CommandLine是由NewFlagSet()
创建的一个全局的FlagSet,再看看FlagSet的定义:
// A FlagSet represents a set of defined flags.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
Usage func()
// SortFlags is used to indicate, if user wants to have sorted flags in
// help/usage messages.
SortFlags bool
// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist
name string
parsed bool
actual map[NormalizedName]*Flag
orderedActual []*Flag
sortedActual []*Flag
formal map[NormalizedName]*Flag
orderedFormal []*Flag
sortedFormal []*Flag
shorthands map[byte]*Flag
args []string // arguments after flags
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
interspersed bool // allow interspersed option/non-option args
normalizeNameFunc func(f *FlagSet, name string) NormalizedName
addedGoFlagSets []*goflag.FlagSet
}
FlagSet是一个Flag的集合,几乎所有的pflag操作都是借助FlagSet的方法来实现的,默认定义了一个全局的FlagSet:CommandLine,我们拿pflag.IntVarP()
方法看看定义:
// IntVarP is like IntVar, but accepts a shorthand letter that can be used after a single dash.
func IntVarP(p *int, name, shorthand string, value int, usage string) {
CommandLine.VarP(newIntValue(value, p), name, shorthand, usage)
}
// VarPF is like VarP, but returns the flag created
func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag {
// Remember the default value as a string; it won't change.
flag := &Flag{
Name: name,
Shorthand: shorthand,
Usage: usage,
Value: value,
DefValue: value.String(),
}
f.AddFlag(flag)
return flag
}
// VarP is like Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
f.VarPF(value, name, shorthand, usage)
}
其内部调用的就是CommandLine.一般使用全局的FlagSet会更加方便。
我们也可以定义自己的FlagSet:调用NewFlagSet()
方法:
var output string
flagset := pflag.NewFlagSet("myflag", pflag.ContinueOnError)
flagset.StringVar(&output, "output", "txt", "Output File Format")
flagset.Parse(os.Args[1:]) // 调用的是自己创建的FlagSet的parse方法!
fmt.Println("output = ", output)
接下来看看源码里的Flag相关定义:
在pflag包里,一个命令行参数会解析为一个Flag类型的变量,Flag定义如下:
// A Flag represents the state of a flag.
type Flag struct {
Name string // name as it appears on command line (flag长选项的名称)
Shorthand string // one-letter abbreviated flag (flag短选项的名称,一个字母)
Usage string // help message (flag的使用帮助信息)
Value Value // value as set (flag的值)
DefValue string // default value (as text); for usage message (flag的默认值)
Changed bool // If the user set the value (or if left to default) (记录flag的值是否被设置过)
NoOptDefVal string // default value (as text); if the flag is on the command line without any options (当flag出现在命令行,但是没有指定选项值时的默认值)
Deprecated string // If this flag is deprecated, this string is the new or now thing to use (如果该flag被废弃,显示的使用帮助信息)
Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text (为true,则在help输出信息中隐藏这个flag)
ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use(如果该flag的短选项被废弃,显示的使用帮助信息)
Annotations map[string][]string // used by cobra.Command bash autocomple code (注解,用于cobra的bash自动补全)
}
Flag的值是一个Value类型的接口,定义如下:
// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
type Value interface {
String() string
Set(string) error
Type() string
}
也就是说只要实现了Value接口的结构,就是一个新的类型值,我们拿源码里的String类型看看:
func (s *stringValue) Set(val string) error {
*s = stringValue(val)
return nil
}
func (s *stringValue) Type() string {
return "string"
}
func (s *stringValue) String() string { return string(*s) }
这里简单的自定义一个新类型:
type Person struct {
Name string
Age int
}
func newPersonValue(val Person, p *Person) *Person {
*p = val
return (*Person)(p)
}
func (p *Person) Set(val string) error {
strs := strings.Split(val, "@")
p.Name = strs[0]
p.Age, _ = strconv.Atoi(strs[1])
return nil
}
func (p *Person) Type() string {
return "Person"
}
func (p *Person) String() string {
return fmt.Sprintf("%s@%d", p.Name, p.Age)
}
func PersonVarP(p *Person, name, shorthand string, value Person, usage string) {
pflag.CommandLine.VarP(newPersonValue(value, p), name, shorthand, usage)
}
var personFlag Person
func init() {
PersonVarP(&personFlag, "person", "p", Person{Name: "abc", Age: 12}, "input person msg")
}
func main() {
pflag.Parse()
fmt.Printf("person = %+v\n", personFlag)
}
测试如下:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go
person = {Name:abc Age:12}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -p zhangsan@18
person = {Name:zhangsan Age:18}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --person lisi@27
person = {Name:lisi Age:27}
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -h
Usage of /tmp/go-build1927169625/b001/exe/pflag_fast:
-p, --person Person input person msg (default abc@12)
pflag: help requested
exit status 2
4. 使用方法
- 多种命令行参数定义方式
定义方式按flag值存储位置分为两种:保存在指针或绑定到变量
- flag值保存在指针
- 支持长选项,默认值和使用说明文本
比如:
var name = flag.String("name", "lucas", "Input your name") func main() { flag.Parse() fmt.Println("name = ", *name) }
- 支持长选项,短选项,默认值和使用说明文本
比如:
var name = flag.StringP("name", "n", "lucas", "Input your name") func main() { flag.Parse() fmt.Println("name = ", *name) }
- 支持长选项,默认值和使用说明文本
- flag值绑定到变量
- 支持长选项,默认值和使用说明文本
比如:
var name string func main() { flag.StringVar(&name, "name", "lucas", "Input your name") flag.Parse() fmt.Println("name = ", name) }
- 支持长选项,短选项,默认值和使用说明文本
比如:
从上面的函数命名中可以总结出以下规律:var name string func main() { flag.StringVarP(&name, "name", "n", "lucas", "Input your name") flag.Parse() fmt.Println("name = ", name) }
- 函数名带P的说明是支持短选项的
- 函数名带Var的说明是将flag绑定到变量,否则则是存储在指针中
- 支持长选项,默认值和使用说明文本
- 定义flag使用flag.String(),Bool(),Int()等,后缀可以带Var,P等
- 所有flag定义完成后,需要调用
flag.Parse()
去解析命令行参数 - 使用
Get<Type>
获取参数的值
可以使用Get<Type>
的方法来获取flag的值,比如GetString(),GetInt()
,其中<Type>
表示pflag支持的所有类型。
需要注意的是,要获取的flag必须存在且类型必须和<Type>
一致,比如获取上述的name,就可以使用GetString("name")
来获取
注意:
这里调用这些Get<Type>
方法都是FlagSet这个结构的方法集里的,以GetString为例,它的方法定义是这样的:
func (f *FlagSet) GetString(name string) (string, error)
而我们使用全局变量CommandLine调用时候需要这样使用:name, err := flag.CommandLine.GetString("name")
- 获取参数
参数在标签flag之后,在调用pflag.Parse()之后,可以调用pflag提供的几个方法用来获取非flag的参数:
// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument
// after flags have been processed.
func Arg(i int) string
// Args returns the non-flag command-line arguments.
func Args() []string
// NArg is the number of arguments remaining after flags have been processed.
func NArg() int
Arg(i int)
: 返回第i个非flag参数
Args()
: 返回所有非flag的参数
NArg()
: 返回非flag的参数个数
使用示例如下:
var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var stringpFlag = pflag.StringP("stringpflag", "s", "stringpflag", "stringp flag usage")
var intFlag int
var boolFlag bool
func init() {
pflag.IntVarP(&intFlag, "intflag", "i", 0, "int flag usage")
pflag.BoolVar(&boolFlag, "boolflag", false, "bool flag usage")
}
func main() {
pflag.Parse()
fmt.Printf("arg个数是: %v\n", pflag.NArg())
fmt.Printf("arg: %v\n", pflag.Args())
fmt.Printf("第1个arg是:%v\n", pflag.Arg(0))
}
执行如下:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --stringflag Hello arg0 arg1 arg2
arg个数是: 3
arg: [arg0 arg1 arg2]
第1个arg是:arg0
- 没有给flag设置选项默认值(Setting no option default values for flags)
当你创建了一个flag后,可以给这个flag设置pflag.NoOptDefVal
.
如果一个flag具有NoOptDefVal,并且该flag在命令行上没有传递这个flag的值,那么这个flag的值就被设置为NoOptDefVal指定的值。
示例:
var port = pflag.IntP("port", "p", 6379, "redis port")
func main() {
pflag.Lookup("port").NoOptDefVal = "8080"
pflag.Parse()
fmt.Printf("redis port = %v\n", *port)
}
执行如下:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go
redis port = 6379
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --port 8888
redis port = 8080
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --port
redis port = 8080
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --port=8888
redis port = 8888
总结结果如下表:
命令行flag | 解析结果 |
---|---|
–port=8888 | port=8888 |
–port | port=8080 |
[nothing] | port=6379 |
注意:
这里需要注意一点,就是指定了NoOptDefVal后,在命令行传递flag时,不能再用诸如--flag xxx
,要用--flag=xxx
,因为--flag xxx
会被认为是没有设置值,从而使用了NoOptDefVal的值,xxx则会被认为是arg
- 命令行flag格式
pflag包的-
与--
是不同的,-
表示短选项,--
表示长选项(标准库flag包-
和--
作用是一样的)。
pflag包支持以下几种格式:
--flag // 支持布尔类型flag,或者设置了NoOptDefVal的flag
--flag x // 只支持没有设置NoOptDefVal的flag(这个上面的"注意"也有提到原因)
--flag=x
注意:pflag遇到
--
后会停止解析,这一点不同于flag包
- 改变或标准化flag名
pflag允许在构建代码或者使用命令行时标准化flag名,比如我们创建了flag为my-flag,但是用户传递时候写错成my.flag或者my_flag,也可以正常识别:
func wordSepNormailzeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return pflag.NormalizedName(name)
}
var myFlag = pflag.String("my-flag", "myflag", "myflag test")
func main() {
pflag.CommandLine.SetNormalizeFunc(wordSepNormailzeFunc)
pflag.Parse()
fmt.Println("myFlag = ", *myFlag)
}
执行如下:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --my-flag=hello
myFlag = hello
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --my_flag hello
myFlag = hello
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --my.flag hello
myFlag = hello
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --my+flag hello
unknown flag: --my+flag
Usage of /tmp/go-build951017333/b001/exe/pflag_fast:
--my.flag string myflag test (default "myflag")
-p, --port int redis port (default 6379)
--stringflag string string flag usage (default "stringflag")
unknown flag: --my+flag
exit status 2
- 弃用flag或flag缩写
pflag支持设置flag或其简写为弃用的,弃用的flag在帮助文本中会被隐藏,并且在使用弃用的flag时会打印提示信息。
示例如下:
var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var port = pflag.IntP("port", "p", 6379, "redis port")
var intFlag = pflag.IntP("intflag", "i", 0, "int flag usage")
func main() {
pflag.CommandLine.MarkDeprecated("port", "please use --new-port instead")
// 只弃用缩写,保留长选项
pflag.CommandLine.MarkShorthandDeprecated("intflag", "please use --intflag only")
pflag.Parse()
}
执行如下:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --port 1234
Flag --port has been deprecated, please use --new-port instead
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --help
Usage of /tmp/go-build2446988640/b001/exe/pflag_fast:
--stringflag string string flag usage (default "stringflag")
pflag: help requested
exit status 2
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go -i 12
Flag shorthand -i has been deprecated, please use --intflag only
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --intflag 12
注意:usage文本是必需的,并且不应该为空
- 隐藏flag
pflag包支持将flag标记为隐藏的,这样子在程序内部这个flag仍是正常运行的,但是不会显示在帮助文本中。
var stringFlag = pflag.String("stringflag", "stringflag", "string flag usage")
var port = pflag.IntP("port", "p", 6379, "redis port")
var intFlag = pflag.IntP("intflag", "i", 0, "int flag usage")
func main() {
pflag.CommandLine.MarkHidden("port")
pflag.Parse()
}
执行help结果:
lucas@Z-NB-0406:~/workspace/test/pflagtest$ go run pflag_fast.go --help
Usage of /tmp/go-build739458557/b001/exe/pflag_fast:
-i, --intflag int int flag usage
--stringflag string string flag usage (default "stringflag")
pflag: help requested
exit status 2
- 禁用flag排序
pflag支持在显示帮助文本时禁用flag排序,比如:
pflag.BoolP("verbose", "v", false, "verbose output")
pflag.String("coolflag", "yeaah", "it's really cool flag")
pflag.Int("usefulflag", 777, "sometimes it's very useful")
pflag.CommandLine.SortFlags = false
pflag.PrintDefaults()
输出如下(按定义的顺序输出):
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
如果设置pflag.CommandLine.SortFlags = true
,输出如下:
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
-v, --verbose verbose output
- 支持Go的flag包
import (
goflag "flag"
flag "github.com/spf13/pflag"
)
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
func main() {
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
}
更多推荐
所有评论(0)