Go CLI Intro

flag

命令行flag解析

  • flag 长短选项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    func main() {
    var name string

    // -flag: 仅支持布尔类型
    // -flag x: 仅支持非布尔类型
    // -flag=x: 均支持
    // 长短选项 分开两次调用
    flag.StringVar(&name, "name", "Golang flag tour", "help")
    flag.StringVar(&name, "n", "Golang flag tour", "help")
    flag.Parse()

    log.Printf("name: %s", name)
    }

    // ➜ go run flag/demo.go -name='hello world' -n 'hello world'
    // 2021/09/11 18:26:39 name: hello world
  • flag 子命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    func main() {
    var name string

    flag.Parse()
    args := flag.Args()
    if len(args) <= 0 {
    return
    }

    switch args[0] {
    case "go":
    goCmd := flag.NewFlagSet("go", flag.ExitOnError)
    goCmd.StringVar(&name, "name", "Golang language", "help")
    _ = goCmd.Parse(args[1:])
    case "py":
    pyCmd := flag.NewFlagSet("py", flag.ExitOnError)
    pyCmd.StringVar(&name, "name", "Python language", "help")
    _ = pyCmd.Parse(args[1:])
    }

    log.Printf("name: %s", name)
    }

    // ➜ go run flag/flag_sub_cli.go go -name=golang
    // 2021/09/11 18:39:47 name: golang
    //
    // ➜ go run flag/flag_sub_cli.go go -n=python
    // flag provided but not defined: -n
    // Usage of go:
    // -name string
    // help (default "Golang language")
    // exit status 2
  • flag 源码分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    # flag.Parse

    // CommandLine is the default set of command-line flags, parsed from os.Args.
    // The top-level functions such as BoolVar, Arg, and so on are wrappers for the
    // methods of CommandLine.
    var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

    // Parse parses the command-line flags from os.Args[1:]. Must be called
    // after all flags are defined and before flags are accessed by the program.
    func Parse() {
    // Ignore errors; CommandLine is set for ExitOnError.
    CommandLine.Parse(os.Args[1:])
    }

    # FlagSet.Parse
    > parse异常分流处理
    // 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:
    if err == ErrHelp {
    os.Exit(0)
    }
    os.Exit(2)
    case PanicOnError:
    panic(err)
    }
    }
    return nil
    }

    # FlagSet.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] != '-' {
    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] == '=' {
    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
    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
    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
    }

Cobra