您的位置 首页 golang

golang标准库之flag

flag包实现了简单的命令行参数解析,支持bool、int、int64、uint、uint64、float64、string和time.Duration八种类型的命令行解析。

使用方法

注册flag流程如下:

import "flag"var ip = flag.Int("flagname", 1234, "help message for flagname") // 返回指针类型,访问时需要加*fmt.Println("ip has value ", *ip)var flagvar intfunc init() {    flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") // 放在init函数中,确保初始化时完成flag的注册fmt.Println("flagvar has value ", flagvar)}

在所有flag都注册之后,调用:

flag.Parse()

需要注意的几点:

  • flag包对于其它类型支持-flag x-flag=x两种情况;
  • flag包对于隐式bool类型支持-flag,默认为True
  • flag包对于显式bool类型支持-flag=false
    显式bool类型的支持并非flag包实现,而是调用strconv包实现的
bool类型支持的bool类型显示字符串
true"1", "t", "T", "true", "TRUE", "True"
false"0", "f", "F", "false", "FALSE", "False"

源码学习

接口设计
type Value interface {    String() string    Set(string) error}type Getter interface {    Value    Get() interface{}}// ErrorHandling定义了解析错误时FlagSet.Parse怎么处理type ErrorHandling intconst (    ContinueOnError ErrorHandling = iota // Return a descriptive error.    ExitOnError                          // Call os.Exit(2).    PanicOnError                         // Call panic with a descriptive error.)// 结构体Flag表示flag的状态type Flag struct {    Name     string // name as it appears on command line    Usage    string // help message    Value    Value  // value as set    DefValue string // default value (as text); for usage message}// FlagSet表示一组flag,空值的FlagSet没有name,但是包含ContinueOnError。// Flag的names在FlagSet内唯一,定义已经存在的name导致panic。type FlagSet struct {    name          string    parsed        bool    actual        map[string]*Flag    formal        map[string]*Flag    args          []string // arguments after flags    errorHandling ErrorHandling    output        io.Writer // nil means stderr; use out() accessor    // 解析错误时调用Usage函数,也可以替换成错误处理函数。    // Usage函数调用之后,会根据ErrorHandling的值作出相应处理。    Usage func()}
1. import "flag"

import 后会执行初始化FlagSet的操作

var CommandLine = NewFlagSet(os.Args[0], ExitOnError) // 实例化FlagSet为CommandLine,注册errorHandling为解析错误时退出程序func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {    f := &FlagSet{        name:          name,        errorHandling: errorHandling,    }    f.Usage = f.defaultUsage    return f}
2. 注册flag

这部分主要工作是把nameflag以键值对存入字典formal,用于在Parse阶段查询。以int类型为例,注册flag的方法主要有4个,如下所示,本质上都是在调用func (f *FlagSet) Var(value Value, name string, usage string)方法。

func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {    f.Var(newIntValue(value, p), name, usage)}func IntVar(p *int, name string, value int, usage string) {    CommandLine.Var(newIntValue(value, p), name, usage)}func (f *FlagSet) Int(name string, value int, usage string) *int {    p := new(int)    f.IntVar(p, name, value, usage)    return p}func Int(name string, value int, usage string) *int {    return CommandLine.Int(name, value, usage)}
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] // 查询formal字典,是否存在新flag的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 {  // formal为空时,需要初始化,否则无法填入        f.formal = make(map[string]*Flag)    }    f.formal[name] = flag // 执行成功,当前flag填入formal}

其中入参Value接口定义如下:

type intValue intfunc newIntValue(val int, p *int) *intValue {    *p = val    return (*intValue)(p)}func (i *intValue) Set(s string) error {    v, err := strconv.ParseInt(s, 0, strconv.IntSize)    if err != nil {        err = numError(err)    }    *i = intValue(v)    return err}func (i *intValue) Get() interface{} { return int(*i) }func (i *intValue) String() string { return strconv.Itoa(int(*i)) }
3. Parse

Parse函数循环调用parseOne,每次解析一个flag,解析失败则触发errorHandling

func Parse() {    CommandLine.Parse(os.Args[1:]) // os.Args[0]是程序名,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 { // err非空时,根据errorHandling执行相应操作        case ContinueOnError:            return err        case ExitOnError:            os.Exit(2)        case PanicOnError:            panic(err)        }    }    return nil}

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] // ---------------解析flag    if len(s) < 2 || s[0] != '-' { // 必须以'-'开头才能识别        return false, nil    }    numMinuses := 1    if s[1] == '-' {        numMinuses++        if len(s) == 2 { // "--" 直接返回,停止解析            f.args = f.args[1:]            return false, nil        }    }    name := s[numMinuses:] // -flag -> flag, --flag -> flag, 去除开头的一个或两个"-", 存入name    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:] // ---------------解析value    hasValue := false    value := ""    for i := 1; i < len(name); i++ { // equals cannot be first        if name[i] == '=' { // 解析-flag=value, --flag=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() { // bool类型,支持-flag和-flag=false两种        if hasValue {            if err := fv.Set(value); err != nil { // 解析-flag=false形式                return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err)            }        } else {            if err := fv.Set("true"); err != nil { // 解析-flag,默认为True                return false, f.failf("invalid boolean flag %s: %v", name, err)            }        }    } else { // 其它类型,支持-flag=123、-flag 123两种        if !hasValue && len(f.args) > 0 {            hasValue = true            value, f.args = f.args[0], f.args[1:] // f.args = f.args[1:],索引0为该flag对应的值,[1:]存入放f.args继续解析        }        if !hasValue {            return false, f.failf("flag needs an argument: -%s", name)        }        if err := flag.Value.Set(value); err != nil { // value写入变量            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 // 解析之后的有效flag存入f.actual    return true, nil}

文章来源:智云一二三科技

文章标题:golang标准库之flag

文章地址:https://www.zhihuclub.com/2929.shtml

关于作者: 智云科技

热门文章

发表回复

您的电子邮箱地址不会被公开。

网站地图