## 1、参数种类
根据参数是否为布尔型,可以分为两种:
- 布尔型参数:如 `--debug`,后面不用再接具体的值,指定就为 True,不指定就为 False非布尔型参数
- 非布尔型参数:非布尔型,有可能是int,string 等其他类型,如 `--name jack` ,后面可以接具体的参数值
flag 包,在使用上,并没有没有长短选项之别,根据参数名的长短,还可以分为:
- 长参数:比如 `--name jack` 就是一个长参数,参数名前有两个 -
- 短参数:通常为一个或两个字母(是对应长参数的简写),比如 `-n` ,参数名前只有一个 -
## 2、示例
### 2.1 入门示例
```golang
package main
import (
"flag"
"fmt"
)
func main(){
var name string
flag.StringVar(&name, "name", "jack", "your name")
flag.Parse() // 解析参数
fmt.Println(name)
}
```
`flag.StringVar` 定义了一个字符串参数,它接收几个参数
- 第一个参数 :接收值后,存放在哪个变量里,需为指针
- 第二个参数 :在命令行中使用的参数名,比如 --name jack 里的 name
- 第三个参数 :若命令行中未指定该参数值,那么默认值为 jack
- 第四个参数:记录这个参数的用途或意义
运行以上程序,输出如下
```shell
$ go run demo.go
jack
$ go run demo.go --name wangbm
wangbm
```
#### 2.2 改进一下
如果你的程序只接收很少的几个参数时,上面那样写也没有什么问题。
但一旦参数数量多了以后,一大堆参数解析的代码堆积在 main 函数里,影响代码的可读性、美观性。
建议将参数解析的代码放入 `init` 函数中,`init` 函数会先于 `main` 函数执行。
```golang
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "jack", "your name")
}
func main(){
flag.Parse()
fmt.Println(name)
}
```
#### 2.3 自定义类型
flag 包支持的类型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。
这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 intValue,String 类型的参数被封装为 stringValue。
这些后端的类型都实现了 flag.Value 接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value 接口和 Flag 类型的代码:
```golang
type Value interface {
String() string
Set(string) error
}
// Flag 类型
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set 是个 interface,因此可以是不同类型的实例。
DefValue string // default value (as text); for usage message
}
func Var(value Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
```
想要实现自定义类型的参数,其实只要 Var 函数的第一个参数对象实现 flag.Value接口即可
```golang
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
// 如何解析参数值
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) String() string {
return strings.Join([]string(*s), ",")
}
```
比如我想实现如下效果,传入的参数是一个字符串,以逗号分隔,flag 的解析时将其转成 slice。
```
$ go run demo.go -members "Jack,Tom"
[Jack Tom]
```
那我可以这样子编写代码
```golang
var members []string
type sliceValue []string
func newSliceValue(vals []string, p *[]string) *sliceValue {
*p = vals
return (*sliceValue)(p)
}
func (s *sliceValue) Set(val string) error {
// 如何解析参数值
*s = sliceValue(strings.Split(val, ","))
return nil
}
func (s *sliceValue) String() string {
return strings.Join([]string(*s), ",")
}
func init() {
flag.Var(newSliceValue([]string{}, &members), "members", "会员列表")
}
func main(){
flag.Parse()
fmt.Println(members)
}
```
#### 2.4 总结
flag 在绝大多数场景下,它是够用的,但如果要支持更多的命令传入格式,flag 可能并不是最好的选择。
那些在标准库不能解决的场景,往往会有相应的Go爱好者提供第三方解决方案。`cobra` 就是一个非常不错的库。
它能够支持 flag 不能支持的功能,比如 `支持短选项`,`支持子命令` 等等,后面找个机会再好好写一下。
命令行参数的解析:flag 库详解