2021-08-29 02:34:57

go 命令行参数解析

[TOC]

go标准库-flag

命令行的风格

由于一些历史原因,Unix 出现过很多不同的分支,命令行的风格也因此有很多标准,比如:

  • Unix 风格,选项采用单 - 加一个字母,比如 -v,短选项就是它,优点是足够简洁;
  • BSD 风格,选项没有 -,没有任何的前缀;
  • GNU 风格,采用 --,如 --version,长选项,扩展性好,但是要多打几个字母;

查看系统进程有两种写法:
ps aux(BSD 风格) 和 ps -elf(Unix 风格)。

POSIX 的命令行风格算是取长补短的集合吧。什么是 POSIX 风格?可以查看这篇文档命令参数语法。它同时提供了长短选项的标准。

flag

flaggo标准库,简单易用。

简单示例:

package main import ( "flag" ) var ( help bool // web服务 configPath string // 配置文件 ) func init() { flag.BoolVar(&help, "h", false, "帮助") flag.StringVar(&configPath, "config", "./config.ini", "配置文件地址") } func main() { flag.Parse() if help { flag.Usage() return } //to do someting }

说明:
flag.BoolVar(&help, “h”, false, “帮助”)
第一个参数 是绑定的变量指针。
第二个参数h是显示在命令行中的参数,可以是-h true 或者--h=true
第三个参数false是默认值,
第四个参数是参数说明。flag.Usage() 会用到。

除了布尔类型,Flag 的类型还有整数(int、int64、uint、uint64)、浮点数(float64)、字符串(string)和时长(time.Duration)。

扩展类型

分析源码

可以参考 flag 包内置类型的实现思路,比如 flag.DurationVarDuration不是基础类型,解析结果是存放到了 time.Duration 类型中,可能更有参考价值。
进入到 flag.DurationVar 查看源码,如下:

func DurationVar(p *time.Duration, name string, value time.Duration, usage string) { CommandLine.Var(newDurationValue(value, p), name, usage) }

通过 newDurationValue 创建了一个类型为 durationValue 的变量(type durationValue time.Duration),并传入到了 CommandLine.Var 方法中。
如果继续往下追,会根据 Value 创建一个 Flag 变量。 如下:

func (f *FlagSet) Var(value Value, name string, usage string) { flag := &Flag{name, usage, value, value.String()} ... }

Var 的定义可以看出,它的第一个参数类型是 Value 接口类型,也就说,durationValue 是实现了 Value 接口的类型。

// Value is the interface to the dynamic value stored in a flag. // (The default value is represented as a string.) // // If a Value has an IsBoolFlag() bool method returning true, // the command-line parser makes -name equivalent to -name=true // rather than using the next command-line argument. // // Set is called once, in command line order, for each flag present. // The flag package may call the String method with a zero-valued receiver, // such as a nil pointer. type Value interface { String() string Set(string) error }

那么,durationValue 的实现代码如何?

type durationValue time.Duration func newDurationValue(val time.Duration, p *time.Duration) *durationValue { *p = val return (*durationValue)(p) } func (d *durationValue) Set(s string) error { v, err := time.ParseDuration(s) if err != nil { err = errParse } *d = durationValue(v) return err } func (d *durationValue) Get() interface{} { return time.Duration(*d) } func (d *durationValue) String() string { return (*time.Duration)(d).String() }

核心在两个地方。
一个是创建新类型变量时,要使用传入的变量地址创建新类型变量,以实现将解析结果放到其中,让前端能获取到,二是 Set 方法中实现命令行传入字符串的解析

实现示例

有些命令行参数 是可以限制值的内容的,类似于枚举,譬如 --from=, 可选值只有baidu,sogou,yahoo

怎么实现呢?

先定义结构体

type stringEnumValue struct {
    options []string
    p   *string
}

实现Value接口

func newStringEnumValue(val string, p *string, options []string) *StringEnumValue { *option = val return &stringEnumValue{options: options, p: p} } func (s *StringEnumValue) Set(v string) error { for _, option := range s.options { if v == option { *(s.p) = v return nil } } return fmt.Errorf("must be one of %v", s.options) } func (s *StringEnumValue) String() string { return *(s.p) }

使用

var from string func init() { flag.Var( newStringEnumValue( "from", // 默认值 &from, []string{"baidu", "sogou", "yahoo"}, ), "baidu", `from engine (default: "baidu")`, ) } func main() { flag.Parse() fmt.Println(option) }

子命令

其他库

除了标准库 flag 外,也有不少的第三方库。比如,为了替代 flag 而生的 pflag,它支持 POSIX 风格的命令行解析。
更多与命令行处理相关的库,可以打开 awesome-go#command-line 命令行一节查看,star 最多的是 spf13/cobraurfave/cli ,与 flag / pflag 相比,它们更加复杂,是一个完全的全功能的框架。

本文链接:http://blog.go2live.cn/post/go-flag.html

-- EOF --