深入了解Go flag

一、前言

flag主要用以对命令行工具的参数的解析获取,其源码位于/flag/flag.go中。

二、flag使用

flag一般使用方法:

(1)声明变量并绑定

有两种方式:

①Var形式

var (
    _para1 string
    _para2 int
    _para3 bool
    ...
)

func init{
    flag.StringVar(&_para1, "para1", "", "para1")
    flag.IntVar(&_para2, "para2", 0, "para2")
    flag.IntVar(&_para3, "para3", false, "para3")
    ...
}

②不带Var格式

var (
_para1 *string
_para2 *int
_para3 *bool

)

func init(
    _para1=flag.String( "para1", "", "para1")
    _para2=flag.Int("para2", 0, "para2")
    _para3=flag.Bool("para3",false,"para3")
    ...
)

两者的功能是一致的,具体原因后面说。

(2)main中Parse

func main(){
    flag.Parse()
    ...
}

调用flag.Parse()后参数的值会解析到绑定的变量中。

(3)使用

./test -para1 value1 -para2 1 -para3 true

三、flag源码

1.绑定

绑定有两种形式,我们以string的处理做说明,其他格式的,可参考具体源码。

// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func (f *FlagSet) StringVar(p *string, name string, value string, usage string) {
    f.Var(newStringValue(value, p), name, usage)
}

// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
    CommandLine.Var(newStringValue(value, p), name, usage)
}

// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func (f *FlagSet) String(name string, value string, usage string) *string {
    p := new(string)
    f.StringVar(p, name, value, usage)
    return p
}

// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
    return CommandLine.String(name, value, usage)
}

我们可以看到,两者调用的均为Var func,其他类型的参数最终也是调用Var来处理。不同之处是,String会new一个string的指针,然后用声明的变量指针指向这个指针的值,而StringVar是直接使用我们传入的指针。

newStringValue主要是完成参数的初始化设置,即提供的默认值设置,并进行相应的类型转换。

func (f *FlagSet) Var(value Value, name string, usage string) {
    // Remember the default value as a string; it won't change.
    flag := &Flag{name, usage, value, value.String()}
    _, alreadythere := f.formal[name]
    if alreadythere {//若已存在则panic报错
        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
}

Var中完成flag的封装,以name为key存入format(map)中。若name已存在,说明重复定义。

2.解析

// Parse parses flag definitions from the argument list, which should not
// include the command name. Must be called after all flags in the FlagSet
// are defined and before flags are accessed by the program.
// The return value will be ErrHelp if -help or -h were set but not defined.
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实现。解析完一个flag才继续下一个,中间发生任何错误,不再继续进行,报错退出。

parseOne

// parseOne parses one flag. It reports whether a flag was seen.
func (f *FlagSet) parseOne() (bool, error) {
    if len(f.args) == 0 {//不能为空
        return false, nil
    }
    s := f.args[0]
    if len(s) < 2 || s[0] != '-' {//key必须包含’-',且长度必须大于1
        return false, nil
    }
    numMinuses := 1
    if s[1] == '-' {
        numMinuses++
        if len(s) == 2 { // "--" terminates the 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)
    }

    // it's a flag. does it have an argument?
    f.args = f.args[1:]
    hasValue := false
    value := ""
    for i := 1; i < len(name); i++ { // equals cannot be first
        if name[i] == '=' {   //处理中包含'='的问题,以'='为界限拆分为key、value两部分
            value = name[i+1:]
            hasValue = true
            name = name[0:i]
            break
        }
    }
    m := f.formal
    flag, alreadythere := m[name] // BUG
    if !alreadythere {//解析到不存在的参数
        if name == "help" || name == "h" { // special case for nice help message.
            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() { // special case: doesn't need an arg
    //bool相关问题,提供value,则设置value,不提供value,则设置为true
        if hasValue {
            if err := fv.Set(value); err != nil {
                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 {
        // It must have a value, which might be the next argument.
        if !hasValue && len(f.args) > 0 {
            // value is the next arg
            //直接取下一个值为key对应的值
            hasValue = true
            value, f.args = f.args[0], f.args[1:]
        }
        if !hasValue {
            return false, f.failf("flag needs an argument: -%s", name)
        }
        //设置值
        if err := flag.Value.Set(value); err != nil {
            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
    return true, nil
}

解析就是找到key及对应的value。key与value总是成对出现的,要想找到value,首要就是先找到key,flag要求key的传入方式是-key,代码就是从找-符号开始。

大致过程如下:

1.寻找key

  • key、value是成对出现的,所以要求len(s)>2
  • key必须以-开头,但不能以--开头
  • 获取-后的部分为name,注意name中可能会包含value

2.处理name中包含value的情况(-key=value

  • 以第一个=为分割符,分成两部分,前一部分为key,后一部分为value

3.确认解析的key对应的flag是否存在,没有则报错

4.对于bool类型的flag,可以不提供value,有value的话用value,没有的话取默认值

5.其他类型的flag必须提供value,当前name中没有value,则取参数中的下一个值

6.设置flag获取到value

7.将flag存入map中

3.更多发现

从以上源码中我们发现了:

(1)参数以"="传值的形式

./test -para1=value1 -para2=1 -para3=true

(2)value中包含"="是允许的

-para1 value1=2//①
-para1=value1=2//②

①是因为代码不会检查value的

②因为代码只要处理了第一个"=",就不再处理了,代码如下:。

for i := 1; i < len(name); i++ { // equals cannot be first
        if name[i] == '=' {   //处理中包含'='的问题,以'='为界限拆分为key、value两部分
            value = name[i+1:]
            hasValue = true
            name = name[0:i]
            break
        }
}

(3)"="传值,则"="右侧不能为空

但是左侧可以为空,虽然没什么实际使用价值。

(4)bool类型可以不传value

对于bool类型的参数,可以不传value,不过会默认设置为true。

(5)传值方式可以混用

实际使用时,两种形式是可以混合使用的,为避免理解错误,非常不建议混用。

./test -para1 value1 -para2=value2 -para3

公众号

鄙人刚刚开通了公众号,专注于分享Go开发相关内容,望大家感兴趣的支持一下,在此特别感谢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章