go 解析命令行参数

356 阅读4分钟

go解析命令行的方法

type函数支持: bool int int64 uint uint64 string float64 duration

八种类型的函数定义除了类型不一样外,其他参数都是一样的,具体定义如下:

func Bool(name string, value bool, usage string) *bool
func Int(name string, value int, usage string) *int
func Int64(name string, value int64, usage string) *int64
func Uint(name string, value uint, usage string) *uint
func Uint64(name string, value uint64, usage string) *uint64
func String(name string, value string, usage string) *string
func Float64(name string, value float64, usage string) *float64
func Duration(name string, value time.Duration, usage string) *time.Duration    

函数参数说明:

  • name: 参数名称
  • value: 默认值
  • usage: 使用方法、帮助文档
func main(){
    boolVal := flag.Bool("testBool", false, "testBool is bool type.")
    flag.Parse()
    
    // 如果使用 -testBool作为参数,控制台将会打印 true, 否则打印 false
    fmt.println(boolVal)
}

typeVar函数

typeVar函数支持: bool int int64 uint uint64 string float64 duration

typeVar函数还另外包含了一种特殊的函数Var(),没有类型修饰。该函数用于绑定用户自定义的参数类型。

具体请看示例:

func main(){
    flag.BoolVar(&config.BoolValue, "bool", false, "This is bool value.")
    flag.IntVar(&config.IntValue, "int", 0, "This is int value.")
    flag.Int64Var(&config.Int64Value, "int64", 0, "This is int64 value.")
    flag.UintVar(&config.UintValue, "uint", 0, "This is uint value.")
    flag.Uint64Var(&config.Uint64Value, "uint64", 0, "This is uint64 value.")
    flag.StringVar(&config.StringValue, "string", "", "This is string value.")
    flag.Float64Var(&config.Float64Value, "float64", 0, "This is float64 value")
    flag.DurationVar(&config.DurationValue, "duration", time.Second * 0, "This is duration value.")
    flag.Var(&config.MyValue, "myValue", "This is my value.")
}

布尔类型为开关类型,后面可以不加参数。

如果不加参数,则一旦出现,默认为true,如果没有出现,则为用户定义的默认数据。

flag.BoolVar(&config.BoolValue, "bool", false, "This is bool value.")
  • 如果使用指令: go run main.go -bool,则config.BoolValue被赋值为:true

  • 如果使用指令: go run main.go,则config.BoolValue为默认值:false

  • 如果修改代码为:

    flag.BoolVar(&config.BoolValue, "bool", true, "This is bool value.")
    

    则使用: go run main.go, config.BoolValue为默认值:true

布尔类型也可以显示声明,格式为-bool=t,t表示true。

bool类型可以接受的值包括:1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False

go run GoFlag.go -bool=t等效于go run GoFlag.go -bool

flag 包解读

flag 包支持的类型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 intValue,String 类型的参数被封装为 stringValue。这些后端的类型都实现了 Value 接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value 接口和 Flag 类型的代码:

// Value 接口
type Value interface {
    String() string
    Set(string) error
}

// Flag 类型
type Flag struct {
    Name     string // name as it appears on command line
    Usage    string // help message
    Value    Value  // value as set 是个 interface,因此可以是不同类型的实例。
    DefValue string // default value (as text); for usage message
}

intValue 等类型实现了 Value 接口,因此可以赋值给 Flag 类型中的 Value 字段,下面是 intValue 类型的定义:

// -- int Value
type intValue int

func 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)
    *i = intValue(v)
    return err
}

func (i *intValue) Get() interface{} { return int(*i) }
func (i *intValue) String() string { return strconv.Itoa(int(*i)) }

所有的参数被保存在 FlagSet 类型的实例中,FlagSet 类型的定义如下:

// A FlagSet represents a set of defined flags.
type FlagSet struct {
    Usage func()

    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
}

Flag 包被导入时创建了 FlagSet 类型的对象 CommandLine:

var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

在程序中定义的所有命令行参数变量都会被加入到 CommandLine 的 formal 属性中,其具体的调用过程如下:

var cliAge = flag.Int("age", 28, "Input Your Age")
func Int(name string, value int, usage string) *int {
    return CommandLine.Int(name, value, usage)
}
func (f *FlagSet) Int(name string, value int, usage string) *int {
    p := new(int)
    f.IntVar(p, name, value, usage)
    return p
}
func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
    f.Var(newIntValue(value, p), name, 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]
    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)
    }
    // 把命令行参数对应的变量添加到 formal 中
    f.formal[name] = flag
}

命令行参数的解析过程则由 flag.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
}

最终由 FlagSet 的 parseOne() 方法执行解析任务:

func (f *FlagSet) parseOne() (bool, error) {
…
flag.Value.Set(value)
…
f.actual[name] = flag
…
}

并在解析完成后由 flag.Value.Set 方法把用户传递的命令行参数设置给 flag 实例,最后添加到 FlagSet 的 actual 属性中。