golang pflag包源码和用法解析

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扩展的用法

golang flag包源码解析

shorthand 用法

pflag原生支持shorthand,在定义flag的时候为其指定shorthand,实现起来更加方便。

与 flag 包不同,在 pflag 包中,选项名称前面的 – 和 - 是不一样的。- 表示 shorthand,-- 表示完整的选项名称

除了最后一个 shorthand,其它的 shorthand 都必须是布尔类型的参数或者是具有默认值的参数

所以:

  1. 对于布尔类型的参数和设置了 NoOptDefVal 的参数可以写成下面的形式:
-o
-o=true
// 注意,下面的写法是不正确的
-o true
  1. 非布尔类型的参数和没有设置 NoOptDefVal 的参数的写法如下:
-g female
-g=female
-gfemale
  1. 注意 – 后面的参数不会被解析:
-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()
}

参考:

Golang命令行参数解析库源码分析:flag VS pflag

Golang之使用Flag和Pflag

Golang : pflag 包简介

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang 1.18版本的Transport源码是一个比较复杂的,涉及到HTTP客户端和服务器之间的网络传输。我将尝试给出一个简单的解析,帮助你理解其中的关键部分。 在源码中,Transport含一个名为Transport的结构体类型。这个结构体有许多字段和方法,用来管理HTTP请求和响应的传输过程。 首先,Transport结构体中的字段有: 1. Proxy:代理服务器的URL,用于将请求发送到代理服务器。 2. DialContext:一个函数类型,用于建立与目标服务器的连接。 3. TLSClientConfig:用于配置TLS(Transport Layer Security)的客户端配置。 4. ForceAttemptHTTP2:一个布尔值,表示是否强制使用HTTP/2协议进行传输。 5. MaxIdleConns:最大空闲连接数。 6. IdleConnTimeout:空闲连接超时时间。 7. ExpectContinueTimeout:等待"Expect: 100-continue"响应的超时时间。 8. MaxResponseHeaderBytes:最大响应头字节数。 除了这些字段外,Transport还有一些方法,其中最重要的是RoundTrip方法。这个方法负责执行HTTP请求并返回响应。它接收一个指向Request结构体的指针作为参数,并返回一个指向Response结构体的指针。 RoundTrip方法内部会根据请求的URL、代理设置、TLS配置等信息建立连接,并发送请求。它还会处理重定向、错误处理、连接复用等逻辑。最终,它将获取到的响应封装在Response结构体中,并返回给调用者。 除了RoundTrip方法,Transport还有其他一些辅助方法,用于管理连接池、设置代理、处理错误等。 总体来说,Transport源码的实现非常复杂,涉及到了很多底层的网络和协议细节。如果你希望深入了解源码的具体实现细节,我建议你仔细阅读源代码,并查阅相关的文档和资料。 希望这个简要的解析能对你有所帮助!如果你有更多关于Transport源码的具体问题,欢迎进一步提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值