flag包属于golang的标准库。本文主要分为两个方面进行介绍,一是使用方法;二是源码解析。
启动服务时,命令行分为三个部分,不同部分不能相互混淆
[cmd] [flags] [args]
cmd – 进程名称
flags – 定义的参数
形式 | 解释 |
-f | (只有布尔类型可以使用该格式,等同于 -f=true) |
-f=x |
(一个 - 符号,使用等号) |
-f x | (一个 - 符号,使用空格) |
--f=x | (两个 - 符号,使用等号) |
--f x | (两个 - 符号,使用空格) |
args – 其它参数
1、定义flag字段
方式一:
定义一个名为flagname的flag,类型为int,默认值是1234
var ip = flag.Int("flagname", 1234, "help message for flagname")
ip是一个int的指针
方式二:
var flagvar int
func init() {
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
}
与方法一不同的是,它将一个变量指针传入了函数中。这两种方式是等价的
2、使用Parse方法,解析flag参数和其它参数。此时已经可以获取到命令行定义的参数了,如果命令行中未定义,则使用默认值
3、通过Args方法以数组的形式获取其它参数
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
}
f.Usage = f.defaultUsage
return f
}
它建立了一个FlagSet对象
type FlagSet struct {
Usage func() // 使用方法回调函数,可以直接外部赋值,否则使用默认函数
name string // 名称,同进程名
parsed bool // 是否已经解析过
actual map[string]*Flag // 实际赋值的flag参数
formal map[string]*Flag // 所有flag参数
args []string // 其它参数
errorHandling ErrorHandling
output io.Writer // 允许将信息输出到别的地方
}
再来看看里面核心的Flag对象
type Flag struct {
Name string // flag参数名称
Usage string // flag参数含义解释
Value Value // flag参数对象
DefValue string // flag参数默认零值
}
Value是一个接口,不同的flag类型需要实现其接口
type Value interface {
String() string
Set(string) error
}
这一步其实是对FlagSet.formal进行填充,我们以bool类型为例:
h2 = flag.Bool("h2", false, "")
func Bool(name string, value bool, usage string) *bool {
return CommandLine.Bool(name, value, usage)
}
// 其中的CommandLine变量其实是初始化时生成的FlagSet对象指针
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
p := new(bool)
f.BoolVar(p, name, value, usage)
return p
}
// 这里先在堆中new一个bool变量出来,并将该指针返回出去,这样程序就可以获取到该flag定义的默认值;一旦后续命令行对该flag有新的定义,也可以通过指针的方式将新值传递回程序中
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
f.Var(newBoolValue(value, p), name, usage)
}
// 我们再看一下newBoolValue函数的定义
func newBoolValue(val bool, p *bool) *boolValue {
*p = val
return (*boolValue)(p)
}
// 它做了两件事情,一是对刚从堆中new出来的变量进行赋值,也就是默认值,二是返回了一个boolValue类型的变量
// 最后一步
func (f *FlagSet) Var(value Value, name string, usage string) {
flag := &Flag{name, usage, value, value.String()}
_, alreadythere := f.formal[name]
if alreadythere {
var msg string
if f.name == "" {
msg = fmt.Sprintf("flag redefined: %s", name)
} else {
msg = fmt.Sprintf("%s flag redefined: %s", f.name, name)
}
fmt.Fprintln(f.Output(), msg)
panic(msg) // Happens only if flags are declared with identical names
}
if f.formal == nil {
f.formal = make(map[string]*Flag)
}
f.formal[name] = flag
}
// 它先生成一个flag对象,然后判断当前的flag是否已经建立好默认值了。如果已经建立好则panic,否则添加进formal变量中。
// 从Var函数的定义,我们了解到boolValue类型实现了value接口,它是这么做的
func (b *boolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
err = errParse
}
*b = boolValue(v) // 类型转化
return err
}
func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) }
// Set函数会在读取命令行参数时使用,String函数会在打印默认帮助信息时使用。
// 目前支持的基础类型有bool,int,int64,string,uint,uint64,float64, time.Duration.
对于自定义类型,只要实现了Value接口也是可以使用的,如下所示:
type headerSlice []string
func (h *headerSlice) String() string {
return fmt.Sprintf("%s", *h)
}
func (h *headerSlice) Set(value string) error {
*h = append(*h, value)
return nil
}
读取的命令行参数其实是覆盖默认值的一个过程,它是由Parse函数实现。
func Parse() {
CommandLine.Parse(os.Args[1:])
}
func (f *FlagSet) Parse(arguments []string) error {
f.parsed = true
f.args = arguments
for {
seen, err := f.parseOne() // 逐个解析参数
if seen { // 解析成功
continue
}
if err == nil {
break
}
// 以下是错误处理
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
// 具体的解析逻辑是由parseOne函数完成
func (f *FlagSet) parseOne() (bool, error) {
if len(f.args) == 0 { // 没有更多参数处理
return false, nil
}
s := f.args[0]
if len(s) < 2 || s[0] != '-' { // 非标准flag格式,跳出
return false, nil
}
numMinuses := 1
if s[1] == '-' {
numMinuses++ // 记录’-’的个数
if len(s) == 2 { // "--" 退出flags
f.args = f.args[1:]
return false, nil
}
}
name := s[numMinuses:] // ’-’后的字符串
if len(name) == 0 || name[0] == '-' || name[0] == '=' { // 异常处理
return false, f.failf("bad flag syntax: %s", s)
}
f.args = f.args[1:] // 移动args的位置
hasValue := false
value := ""
for i := 1; i < len(name); i++ {
if name[i] == '=' { // ‘=’赋值的方式
value = name[i+1:]
hasValue = true
name = name[0:i]
break
}
}
m := f.formal
flag, alreadythere := m[name]
if !alreadythere { // 如果未设置默认值,则直接报错
if name == "help" || name == "h" { // help flag
f.usage()
return false, ErrHelp
}
return false, f.failf("flag provided but not defined: -%s", name)
}
if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // bool flag进行特殊处理
if hasValue {
if err := fv.Set(value); err != nil { // 设置flag参数值
return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)
}
} else {
if err := fv.Set("true"); err != nil {
return false, f.failf("invalid boolean flag %s: %v", name, err)
}
}
} else {
if !hasValue && len(f.args) > 0 { // 空格分隔的形式
// value is the next arg
hasValue = true
value, f.args = f.args[0], f.args[1:] // 移动args的位置
}
if !hasValue {
return false, f.failf("flag needs an argument: -%s", name)
}
if err := flag.Value.Set(value); err != nil { // 设置flag参数值
return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
}
}
if f.actual == nil {
f.actual = make(map[string]*Flag)
}
f.actual[name] = flag // 设置actual变量的值
return true, nil
}
通过Args函数可以获取到其它参数。仔细看parseOne函数,没解析一个flag参数,args变量的index就会往后移动一个或者两个。当没有更多的flag参数需要解析时,剩下的参数均是其它参数。
前两步通常在main函数执行前完成,第三步parse通常在main函数开始时执行,因为只有main函数启动后,才能获取到命令行参数。Parse函数它的主要作用有两个,一是将命令行中的参数值覆盖第二步设定的默认值,二是区分flag参数和其它参数。