背景
希望有一天可以使用go来写命令行软件
os包
os包提供简单的参数引用.
- 全部解析为字符串
- 不区分具名选项与其他参数
- 第0个是程序名
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { if len(os.Args) > 2 { fmt.Println(reflect.TypeOf(os.Args[1])) fmt.Println(os.Args) } }
|
flag包
标准库中的flag包,借助os包获取参数.
并提供一套还算合适的API来定义,解析,获取参数.
功能包括
- 参数名,类型,默认值的设置
- 帮助文档的批量显示(支持自定义)
- 参数的解析
一些缺点:
- 不支持长短参数的区分
- 匿名参数支持不好
- 不负责解决参数的逻辑关系
- 相互冲突的参数
- 相互依赖的参数
- 不同名称,同一意义如何处理
基础使用
支持3种方法获取用户的输入
flag.XxxVar
方法读入到预先定义的变量中
flag.Xxx
方法,返回值是个指针,可以保存在预先定义的变量中
- 无论是否保存到变量中,后续使用
flag.Lookup("选项名")
获取flag的实例,其中 Value
代表flag的值.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| var ( h bool q bool m *string )
func init() { flag.BoolVar(&h, "h", false, "help flag") flag.BoolVar(&q, "q", false, "be quiet")
m = flag.String("mode", "debug", "app `mode`: debug, master")
flag.String("t", "", "test flag") }
func main() { flag.Parse()
if h { fmt.Println("h enabled") } if q { fmt.Println("q enabled") } fmt.Println(*m)
tFlag := flag.Lookup("t") fmt.Println(tFlag.Value) }
|
这样就能使用如下命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| # 开关型参数 ./main # h 为false ./main -h # h 为true
# 不支持短参数,一律认为长参数 ./main -qh # 报错 ./main -q -h # q,h分别为true ./main -mode master # m 为"master" ./main --mode master # m 为"master"
# 带不带等号均可 ./main # m 为默认的"debug" ./main --mode master # m 为"master" ./main --mode=master # m 为"master"
|
显示参数帮助信息
flag.Usage()
是一个自带print功能的函数,使用之即可打印一套格式化后的帮助信息.
且该函数可以被自定义,替换时直接替换掉函数即可.
自定义函数内部,可以用 flag.PrintDefaults()
输出默认的格式化后的帮助信息(进参数部分)
默认的格式化信息包括
- 参数名
- 形式参数名
- 说明语句
- 默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var ( h bool q bool m string s int )
func init() { flag.BoolVar(&h, "h", false, "help flag")
flag.StringVar(&m, "m", "debug", "mapp `mode` (shorthand)") flag.StringVar(&m, "mode", "debug", "app `mode`: debug, master") flag.IntVar(&s, "s", 0, "test flag")
flag.Usage = customUsage }
func main() { flag.Parse()
if h { flag.Usage() } }
func customUsage() { fmt.Fprintln(os.Stderr, `customize usage of main Options: `) flag.PrintDefaults() }
|
特殊情况处理
不同参数名解析到同一个变量
不负责解决冲突,只显示最后一个有效值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var ( m string )
func init() { flag.StringVar(&m, "m", "debug", "mapp `mode` (shorthand)") flag.StringVar(&m, "mode", "debug", "app `mode`: debug, master") }
func main() { flag.Parse()
fmt.Println(m) }
|
使用时效果如下
1 2
| ./main -m v1 -mode v2 # v2 ./main -mode v1 -m v2 # v2
|
相同参数名指向不同的变量
报错
定义了int类型,但输入不是int的
报错
自定义参数解析方式
假设要定义一个类型为 []string
- 需要满足
flag.Value
接口,因此先起一个别名,方便定义方法
- 需要定义的方法有
Set
, String
Set
输入一个string,输出一个error,过程中要将处理结果存入自定义类型
String
用于可视化
- 正常流程
- 声明参数类型
- 解析到该类型的指针中
- 取出内容使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| type sliceValue []string
func (s *sliceValue) Set(val string) error { *s = sliceValue(strings.Split(val, ",")) return nil }
func (s *sliceValue) String() string { return strings.Join([]string(*s), ",") }
var languages sliceValue
func init() { flag.Var(&languages, "l", "programming `languages` that I like") }
func main() { flag.Parse() fmt.Println(*&languages) }
|
匿名参数
flag对匿名参数提供有限的支持
flag.NArgs()
表示匿名参数长度
flag.Args()
获取所有匿名参数列表
flag.Arg(i int)
表示第i个匿名参数
flag对匿名参数的支持不好,体现在
- 不能将匿名参数绑定到变量
- 一旦碰到匿名参数就会停止解析,即便之后有具名参数,写命令时需要非常小心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| var ( h bool m string )
func init() { flag.BoolVar(&h, "h", false, "h flag") flag.StringVar(&m, "m", "debug", "app `mode`: debug,master ") } func main() { flag.Parse() fmt.Println(h) fmt.Println(m)
fmt.Println(flag.Args())
if flag.NArg() > 0 { for i := 0; i < flag.NArg(); i++ { fmt.Println(flag.Arg(i)) } } }
|
使用时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| # 需要将匿名参数写在后边,才能让flag.Args()存储剩余的,未解析的参数 # 然后依靠位置关系0,1,2等确定每个参数的意义 ./main -h -m master hello world # true # master # [hello world] # hello # world
# 从第一个匿名参数起,flag不再解析所有参数 go run main.go -m master hello -h world # false # master # [hello -h world] # hello # -h # world
|
FlagSet
flag包提供的功能本质上基于 FlagSet
类,
只不过向外暴露的一个个函数,调用内部的 FlagSet
实例上的方法.
默认的FlagSet实例使用os.Args中的相关信息来生成.
1 2 3 4 5 6 7 8 9 10 11 12 13
| var CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) var ( h bool )
func init() { CommandLine.BoolVar(&h, "h", false, "h flag") }
func main() { CommandLine.Parse(os.Args[1:]) fmt.Println(h) }
|
一些说明
- 在一个程序中自定义两套FlagSet不是特别容易的事情,
用户几乎无法正好将一些意义的内容放在合适的次序上.
直接用包默认提供的flagset即可
- FlagSet是一个较为复杂的结构体,暂时略过其他的成员和方法解说.
参考
- 主要参考
- 较为详尽的解释