介绍

在写命令行程序(工具、server)时,对命令参数进行解析是常见的需求。各种语言一般都会提供解析命令行参数的方法或库,以方便程序员使用。在 go 标准库中提供了一个包:flag,方便进行命令行解析。

概述


定义flags
  • 标准定义
    (1)flag.Type(name, defValue, usage) 其中Type为String, Int, Bool等;并返回一个相应类型的指针。
    示例:
    var ip = flag.Int("flagname", 1234, "help message for flagname") (2)flag.TypeVar(&flagvar, name, defValue, usage) 将flag绑定在flagvar变量上。
    示例:
  • var newflag int
    flag.IntVar(&newflag, "flagname", 1234, "help message for flagname")
  • 自定义flag
    flag.Var(&flagvar, name, usage) 这种方法要实现flag.Value接口:
  • type Value interface {
      String() string
      Set(string) error
    }
  • 示例:
  • type percentage float32
    func (p *percentage) Set(s string) error {
      v, err := strconv.ParseFloat(s, 32)
      *p = percentage(v)
      return err
    }
    func (p *percentage) String() string { return fmt.Sprintf("%f", *p) }
    
    var pip percentage
    flag.Var(&pop,"pop","popularity")
解析flag

在所有的 flag 定义完成之后,可以通过调用 flag.Parse() 进行解析。
命令行 flag 的语法有如下三种形式:

-flag // 只支持bool类型
-flag=x
-flag x // 只支持非bool类型

其中第三种形式只能用于非 bool 类型的 flag,默认的,提供了 -flag,则对应的值为 true,否则为 flag.Bool/BoolVar 中指定的默认值;如果希望显示设置为 false 则使用 -flag=false。

int 类型可以是十进制、十六进制、八进制甚至是负数;bool 类型可以是1, 0, t, f, true, false, TRUE, FALSE, True, False。Duration 可以接受任何 time.ParseDuration 能解析的类型。

主要类型的方法

flag 包中主要是FlagSet类型。

  • 实例化方式
    NewFlagSet() 用于实例化 FlagSet。预定义的 FlagSet 实例 CommandLine 的定义方式:
// The default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
  • 解析参数
    从参数列表中解析定义的 flag。方法参数 arguments 不包括命令名,即应该是os.Args[1:]。Parse(arguments []string)的源码如下:
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。其解析停止的条件如下:
    (1)第一个为non-flag参数
    当遇到单独的一个"-“或不是”-"开始时,会停止解析。
s := f.args[0]
if len(s) == 0 || s[0] != '-' || len(s) == 1 {
      return false, nil
}
  • 示例:./nginx - -c 或 ./nginx build -c,这两种情况,-c 都不会被正确解析。像该例子中的"-“或build(以及之后的参数),我们称之为 non-flag 参数。
    (2)两个连续的“–”
    当遇到连续的两个”-"开始时,解析停止。
if s[1]  == '-' {
    num_minuses++
    if len(s) == 2 { // "--" terminates the flags
        f.args = f.args[1:]
        return false, nil
    }
}
  • 其中,f.args = f.args[1:]表示每执行成功一次 parseOne,f.args 会少一个。所以,FlagSet 中的 args 最后留下来的就是所有 non-flag 参数。
  • 常见方法
    Arg(i int) 和 Args() 这两个方法就是获取 non-flag 参数的;NArg()获得 non-flag 的个数;NFlag() 获得 FlagSet 中的actual长度(即被设置了的参数个数)。

参考链接