[TOC]
go标准库-flag
命令行的风格
由于一些历史原因,Unix 出现过很多不同的分支,命令行的风格也因此有很多标准,比如:
- Unix 风格,选项采用单 - 加一个字母,比如 -v,短选项就是它,优点是足够简洁;
- BSD 风格,选项没有 -,没有任何的前缀;
- GNU 风格,采用 --,如 --version,长选项,扩展性好,但是要多打几个字母;
查看系统进程有两种写法:
ps aux
(BSD 风格) 和 ps -elf
(Unix 风格)。
POSIX 的命令行风格算是取长补短的集合吧。什么是 POSIX 风格?可以查看这篇文档命令参数语法。它同时提供了长短选项的标准。
flag
flag
是go
标准库,简单易用。
简单示例:
package main
import (
"flag"
)
var (
help bool // web服务
configPath string // 配置文件
)
func init() {
flag.BoolVar(&help, "h", false, "帮助")
flag.StringVar(&configPath, "config", "./config.ini", "配置文件地址")
}
func main() {
flag.Parse()
if help {
flag.Usage()
return
}
//to do someting
}
说明:
flag.BoolVar(&help, “h”, false, “帮助”)
第一个参数 是绑定的变量指针。
第二个参数h
是显示在命令行中的参数,可以是-h true
或者--h=true
第三个参数false是默认值,
第四个参数是参数说明。flag.Usage() 会用到。
除了布尔类型,Flag 的类型还有整数(int、int64、uint、uint64)、浮点数(float64)、字符串(string)和时长(time.Duration)。
扩展类型
分析源码
可以参考 flag
包内置类型的实现思路,比如 flag.DurationVar
。Duration
不是基础类型,解析结果是存放到了 time.Duration
类型中,可能更有参考价值。
进入到 flag.DurationVar
查看源码,如下:
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
CommandLine.Var(newDurationValue(value, p), name, usage)
}
通过 newDurationValue
创建了一个类型为 durationValue
的变量(type durationValue time.Duration
),并传入到了 CommandLine.Var
方法中。
如果继续往下追,会根据 Value
创建一个 Flag
变量。 如下:
func (f *FlagSet) Var(value Value, name string, usage string) {
flag := &Flag{name, usage, value, value.String()}
...
}
从 Var
的定义可以看出,它的第一个参数类型是 Value
接口类型,也就说,durationValue
是实现了 Value
接口的类型。
// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
//
// If a Value has an IsBoolFlag() bool method returning true,
// the command-line parser makes -name equivalent to -name=true
// rather than using the next command-line argument.
//
// Set is called once, in command line order, for each flag present.
// The flag package may call the String method with a zero-valued receiver,
// such as a nil pointer.
type Value interface {
String() string
Set(string) error
}
那么,durationValue
的实现代码如何?
type durationValue time.Duration
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
*p = val
return (*durationValue)(p)
}
func (d *durationValue) Set(s string) error {
v, err := time.ParseDuration(s)
if err != nil {
err = errParse
}
*d = durationValue(v)
return err
}
func (d *durationValue) Get() interface{} { return time.Duration(*d) }
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
核心在两个地方。
一个是创建新类型变量时,要使用传入的变量地址创建新类型变量,以实现将解析结果放到其中,让前端能获取到,二是 Set 方法中实现命令行传入字符串的解析
实现示例
有些命令行参数 是可以限制值的内容的,类似于枚举,譬如 --from=, 可选值只有baidu,sogou,yahoo
怎么实现呢?
先定义结构体
type stringEnumValue struct {
options []string
p *string
}
实现Value
接口
func newStringEnumValue(val string, p *string, options []string) *StringEnumValue {
*option = val
return &stringEnumValue{options: options, p: p}
}
func (s *StringEnumValue) Set(v string) error {
for _, option := range s.options {
if v == option {
*(s.p) = v
return nil
}
}
return fmt.Errorf("must be one of %v", s.options)
}
func (s *StringEnumValue) String() string {
return *(s.p)
}
使用
var from string
func init() {
flag.Var(
newStringEnumValue(
"from", // 默认值
&from,
[]string{"baidu", "sogou", "yahoo"},
),
"baidu",
`from engine (default: "baidu")`,
)
}
func main() {
flag.Parse()
fmt.Println(option)
}
子命令
其他库
除了标准库 flag
外,也有不少的第三方库。比如,为了替代 flag 而生的 pflag,它支持 POSIX 风格的命令行解析。
更多与命令行处理相关的库,可以打开 awesome-go#command-line 命令行一节查看,star 最多的是 spf13/cobra 和 urfave/cli ,与 flag / pflag 相比,它们更加复杂,是一个完全的全功能的框架。