Golang flag包解析

概要

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
}

填写flag参数默认值

这一步其实是对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参数和其它参数。

你可能感兴趣的:(源码分析,golang标准库,golang)