pflag 简介
之前我们讲过golang flag包的源码和基本用法,其实对于命令行参数解析,golang有一个更好用的包,叫做pflag
pflag是Go的flag包的直接替代,实现了POSIX / GNU样式的–flags。pflag是Go的本机标志包的直接替代。如果您在名称“ flag”下导入pflag,则所有代码应继续运行且无需更改。
flag和pflag都是源自于Google,工作原理甚至代码实现基本上都是一样的。 flag虽然是Golang官方的命令行参数解析库,但是pflag却得到更加广泛的应用。 因为pflag相对flag有如下优势:
- 支持更加精细的参数类型:例如,flag只支持uint和uint64,而pflag额外支持uint8、uint16、int32。
- 支持更多参数类型:ip、ip mask、ip net、count、以及所有类型的slice类型,例如string slice、int slice、ip slice等。
- 兼容标准flag库的Flag和FlagSet。
- 原生支持更丰富flag功能:shorthand、deprecated、hidden等高级功能。
默认标志位:--
--flag // boolean flags, or flags with no option default values
--flag x // only on flags without a default value
--flag=x
安装
go get github.com/spf13/pflag
pflag 用法
package main
import flag "github.com/spf13/pflag"
import (
"fmt"
"strings"
)
// 定义命令行参数对应的变量
var cliName = flag.StringP("name", "n", "nick", "Input Your Name")
var cliAge = flag.IntP("age", "a", 22, "Input Your Age")
var cliGender = flag.StringP("gender", "g", "male", "Input Your Gender")
var cliOK = flag.BoolP("ok", "o", false, "Input Are You OK")
var cliDes = flag.StringP("des-detail", "d", "", "Input Description")
var cliOldFlag = flag.StringP("badflag", "b", "just for test", "Input badflag")
func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return flag.NormalizedName(name)
}
func main() {
// 设置标准化参数名称的函数
// 如果我们创建了名称为 --des-detail 的参数,但是用户却在传参时写成了 --des_detail 或 --des.detail 会怎么样?
// 默认情况下程序会报错退出,但是我们可以通过 pflag 提供的 SetNormalizeFunc 功能轻松的解决这个问题
flag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
// 为 age 参数设置 NoOptDefVal 默认值,通过简便的方式为参数设置默认值之外的值
flag.Lookup("age").NoOptDefVal = "25"
// 把 badflag 参数标记为即将废弃的,请用户使用 des-detail 参数
flag.CommandLine.MarkDeprecated("badflag", "please use --des-detail instead")
// 把 badflag 参数的 shorthand 标记为即将废弃的,请用户使用 des-detail 的 shorthand 参数
flag.CommandLine.MarkShorthandDeprecated("badflag", "please use -d instead")
// 在帮助文档中隐藏参数 gender
flag.CommandLine.MarkHidden("badflag")
// 把用户传递的命令行参数解析为对应变量的值
flag.Parse()
fmt.Println("name=", *cliName)
fmt.Println("age=", *cliAge)
fmt.Println("gender=", *cliGender)
fmt.Println("ok=", *cliOK)
fmt.Println("des=", *cliDes)
}
基本用法
可看下这篇文章,使用方式基本上一致,这篇文章我们只分析下pflag扩展的用法
shorthand 用法
pflag原生支持shorthand,在定义flag的时候为其指定shorthand,实现起来更加方便。
与 flag 包不同,在 pflag 包中,选项名称前面的 – 和 - 是不一样的。- 表示 shorthand,-- 表示完整的选项名称
除了最后一个 shorthand,其它的 shorthand 都必须是布尔类型的参数或者是具有默认值的参数
所以:
- 对于布尔类型的参数和设置了 NoOptDefVal 的参数可以写成下面的形式:
-o
-o=true
// 注意,下面的写法是不正确的
-o true
- 非布尔类型的参数和没有设置 NoOptDefVal 的参数的写法如下:
-g female
-g=female
-gfemale
- 注意 – 后面的参数不会被解析:
-oa=35 -- -gfemale
验证:
➜ xcode_go_f go run main.go -n newName -o false -a 30 -- -gfemale
name= newName
age= 25
gender= male
ok= true
des=
// 1. 看到 -o 参数后面设置了false,但是实际打印出来的ok却是为true,说明并没有进行解析
// 2. 命令行指定了 -a 30 ,但是age打印出来却是25,说明没有解析成功
// 3. gender结果还是male,说明 -- 后面的参数并没有被解析
但是这里说一下,flag虽然不是原生支持shorthand,但是可以通过两个flag共享同一变量来间接支持,可以参考官方提供的Example:
golang/src/flag/example_test.go
// Example 2: Two flags sharing a variable, so we can have a shorthand.
// The order of initialization is undefined, so make sure both use the
// same default value. They must be set up with an init function.
var gopherType string
func init() {
const (
defaultGopher = "pocket"
usage = "the variety of gopher"
)
flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)")
}
flag虽然能够通过间接方式实现shorthand,但是flag的数量要翻倍,同时不能避免这两个flag被同时使用的错误用法。 上面flag的shorthand example的pflag版如下:
// Example for shorthand in pflag.
import flag "github.com/spf13/pflag"
var gopherType string
func init() {
const (
defaultGopher = "pocket"
usage = "the variety of gopher"
)
flag.StringVarP(&gopherType, "gopher_type", "g", defaultGopher, usage)
}
NoOptDefVal 用法
pflag 包支持通过简便的方式为参数设置默认值之外的值,实现方式为设置参数的 NoOptDefVal 属性
var cliAge = flag.IntP("age", "a",22, "Input Your Age")
flag.Lookup("age").NoOptDefVal = "25"
下面是传递参数的方式和参数最终的取值:
Parsed Arguments Resulting Value
--age=30 cliAge=30
--age cliAge=25
[nothing] cliAge=22
注意,对于设置了NoOptDefVal的参数, -a 30
,这样使用是不正确的,这点在shorthand用法中已经说过了,不再演示
Normalize 用法
标准化参数的名称
如果我们创建了名称为 --des-detail 的参数,但是用户却在传参时写成了 --des_detail 或 --des.detail 会怎么样?默认情况下程序会报错退出,但是我们可以通过 pflag 提供的 SetNormalizeFunc 功能轻松的解决这个问题:
func wordSepNormalizeFunc(f *flag.FlagSet, name string) flag.NormalizedName {
from := []string{"-", "_"}
to := "."
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return flag.NormalizedName(name)
}
flag.CommandLine.SetNormalizeFunc(wordSepNormalizeFunc)
下面的写法也能正确设置参数了:
--des_detail="person detail"
验证:
➜ xcode_go_f go run main.go -n newName -o false -a 30 --des_detail="person detail"
name= newName
age= 25
gender= male
ok= true
des= person detail
deprecated 用法
把参数标记为即将废弃
在程序的不断升级中添加新的参数和废弃旧的参数都是常见的用例,pflag 包对废弃参数也提供了很好的支持。通过 MarkDeprecated 和 MarkShorthandDeprecated 方法可以分别把参数及其 shorthand 标记为废弃:
// 把 badflag 参数标记为即将废弃的,请用户使用 des-detail 参数
flag.CommandLine.MarkDeprecated("badflag", "please use --des-detail instead")
// 把 badflag 参数的 shorthand 标记为即将废弃的,请用户使用 des-detail 的 shorthand 参数
flag.CommandLine.MarkShorthandDeprecated("badflag", "please use -d instead")
验证:
➜ xcode_go_f go run main.go -n newName -o false -a 30 --des_detail="person detail" -b test
Flag shorthand -b has been deprecated, please use -d instead
Flag --badflag has been deprecated, please use --des-detail instead
name= newName
age= 25
gender= male
ok= true
des= person detail
// 可以看到第2行和第3行的输出,参数废弃的警告
hidden 用法
在帮助文档中隐藏参数
pflag 包还支持在参数说明中隐藏参数的功能:
// 在帮助文档中隐藏参数 badflag
flag.CommandLine.MarkHidden("badflag")
验证:
➜ xcode_go_f go run main.go -h
Usage of /var/folders/fw/x0b6q0fx2znb4cl4v6ch2jk40000gn/T/go-build365710515/b001/exe/main:
-a, --age int[=25] Input Your Age (default 22)
-d, --des.detail string Input Description
-g, --gender string Input Your Gender (default "male")
-n, --name string Input Your Name (default "nick")
-o, --ok Input Are You OK
pflag: help requested
可以看到 Usage 输出并没有 badflag
参数的说明
pflag 练习
练习1
package main
import (
flag "github.com/spf13/pflag" //替换原生的flag,并兼容
"fmt"
)
var flagvar1 int
var flagvar2 bool
func init() {
flag.IntVar(&flagvar1, "varname1", 1, "help message for flagname")
flag.BoolVarP(&flagvar2, "boolname1", "b", true, "help message")
}
func main() {
var ip1 *int = flag.Int("flagname1", 1, "help message for flagname")
var ip2 = flag.IntP("flagname2", "f", 2, "help message")
flag.Parse()
fmt.Println("ip1 has value ", *ip1)
fmt.Println("ip2 has value ", *ip2)
fmt.Println("flagvar1 has value ", flagvar1)
fmt.Println("flagvar2 has value ", flagvar2)
}
$ go build fplag1.go
/pflag1 -h
Usage of ./pflag1:
-b, --boolname1 help message (default true)
--flagname1 int help message for flagname (default 1)
-f, --flagname2 int help message (default 2)
--varname1 int help message for flagname (default 1)
pflag: help requested
练习2
package main
import (
"github.com/spf13/pflag"
"net"
"fmt"
"time"
)
func pflagDefine() {
//64位整数,不带单标志位的
var pflagint64 *int64 = pflag.Int64("number1", 1234, "this is int 64, without single flag")
//64位整数,带单标志位的
var pflagint64p *int64 = pflag.Int64P("number2", "n", 2345, "this is int 64, without single flag")
//这种可以把变量的定义和变量取值分开,适合于struct,全局变量等地方
var pflagint64var int64
pflag.Int64Var(&pflagint64var, "number3", 1234, "this is int64var")
//上面那一种的增加短标志位版
var pflagint64varp int64
pflag.Int64VarP(&pflagint64varp,"number4", "m", 1234, "this is int64varp")
//slice版本,其实是上面的增强版,但是支持多个参数,也就是导成一个slice
var pflagint64slice *[]int64 = pflag.Int64Slice("number5", []int64{1234, 3456}, "this is int64 slice")
//bool版本
var pflagbool *bool = pflag.Bool("bool", true, "this is bool")
//bytes版本
var pflagbyte *[]byte = pflag.BytesBase64("byte64", []byte("ea"), "this is byte base64")
//count版本
var pflagcount *int= pflag.Count("count", "this is count")
//duration版本
var pflagduration *time.Duration = pflag.Duration("duration", 10* time.Second, "this is duration")
//float版本
var pflagfloat *float64 = pflag.Float64("float64", 123.345, "this is florat64")
//IP版本
var pflagip *net.IP = pflag.IP("ip1", net.IPv4(192, 168, 1, 1), "this is ip, without single flag")
//mask版本
var pflagmask *net.IPMask= pflag.IPMask("mask", net.IPv4Mask(255,255,255,128),"this is net mask")
//string版本
var pflagstring *string= pflag.String("string", "teststring", "this is string")
//uint版本
var pflaguint *uint64 = pflag.Uint64("uint64", 12345, "this is uint64")
pflag.Parse()
fmt.Println("number1 int64 is ", *pflagint64)
fmt.Println("number2 int64 is ", *pflagint64p)
fmt.Println("number3 int64var is ", pflagint64var)
fmt.Println("number4 int64varp is", pflagint64varp)
fmt.Println("number5 int64slice is", *pflagint64slice)
fmt.Println("bool is ", *pflagbool)
fmt.Println("byte64 is ", *pflagbyte)
fmt.Println("count is ", *pflagcount)
fmt.Println("duration is ", *pflagduration)
fmt.Println("float is ", *pflagfloat)
fmt.Println("ip1 net.ip is ", *pflagip)
fmt.Println("mask is %s", *pflagmask)
fmt.Println("string is ", *pflagstring)
fmt.Println("uint64 is ", *pflaguint)
}
func main() {
pflagDefine()
}
$ go build pflag2.go
$ ./pflag2 -h
Usage of ./pflag1:
--bool this is bool (default true)
--byte64 bytesBase64 this is byte base64 (default ZWE=)
--count count this is count
--duration duration this is duration (default 10s)
--float64 float this is florat64 (default 123.345)
--ip1 ip this is ip, without single flag (default
--mask ipMask this is net mask (default ffffff80)
--number1 int this is int 64, without single flag (defa
-n, --number2 int this is int 64, without single flag (defa
--number3 int this is int64var (default 1234)
-m, --number4 int this is int64varp (default 1234)
--number5 int64Slice this is int64 slice (default [1234,3456])
--string string this is string (default "teststring")
--uint64 uint this is uint64 (default 12345)
pflag: help requested
练习3
flag.Lookup,flag包中提供了一种类似上述的”配置中心”的机制,但这种机制不需要我们显示注入“flag vars”了,我们只需按照flag提供的方法在其他package中读取对应flag变量的值即可。
$tree flaglookup
flaglookup
├── etcd
│ └── etcd.go
└── main.go
// flag-demo/flaglookup/main.go
package main
import (
"flag"
"fmt"
"time"
"./etcd"
)
var (
endpoints string
user string
password string
)
func init() {
flag.StringVar(&endpoints, "endpoints", "127.0.0.1:2379", "comma-separated list of etcdv3 endpoints")
flag.StringVar(&user, "user", "", "etcdv3 client user")
flag.StringVar(&password, "password", "", "etcdv3 client password")
}
func usage() {
fmt.Println("flagdemo-app is a daemon application which provides xxx service.\n")
fmt.Println("Usage of flagdemo-app:\n")
fmt.Println("\t flagdemo-app [options]\n")
fmt.Println("The options are:\n")
flag.PrintDefaults()
}
func main() {
flag.Usage = usage
flag.Parse()
go etcd.EtcdProxy()
time.Sleep(5 * time.Second)
}
// flag-demo/flaglookup/etcd/etcd.go
package etcd
import (
"flag"
"fmt"
)
func EtcdProxy() {
endpoints := flag.Lookup("endpoints").Value.(flag.Getter).Get().(string)
user := flag.Lookup("user").Value.(flag.Getter).Get().(string)
password := flag.Lookup("password").Value.(flag.Getter).Get().(string)
fmt.Println(endpoints, user, password)
}
[root@localhost flaglookup]# go run main.go -endpoints 192.168.10.69:2379,10.10.12.36:2378 -user tonybai -password xyz123
192.168.10.69:2379,10.10.12.36:2378 tonybai xyz123
flag与pflag混用
混用flag及pflag时,注意使用的方法
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()
}
参考: