2

Golang | Stdlib: Flag 学习笔记

 2 years ago
source link: https://ijayer.github.io/post/tech/code/golang/20180912-stdlib-flag/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

flag 包学习笔记概要:

  • 如何定义命令行参数,即 flags ?
  • flags 解析规则及语法格式 ?
  • flag 包有哪些重要的变量、函数以及类型 ?
  • 如何绑定用户自定义的数据类型作为参数值的类型?

1. Flag

Flag:解析命令行参数,其实现方法是将命令行标志与程序中的某一变量进行绑定,开发人员使用变量在程序中进行逻辑处理。

2. 定义flags

有三种方式定义 flags

2.1. 方式一

通过 flag.Type(name, defValue, usage) *Type {} 方法,该方法返回一个对应数据类型的指针, 其中 Type 为:String、Int、Bool等。

 var port = flag.String("port", "80", "http port.(default:80)")
 var num = flag.Int()
 // ...

2.2. 方式二

通过 flag.TypeVar(&flagvar, name, defValue, usage) Type {} 方法将flag绑定到一个变量, 该方法返回结果为值类型

var num int
func init() {
    flag.IntVar(&num, "num", 1234, "help message for flagname")
    // ...
}

2.3. 方式三

通过 flag.Var() 方法绑定自定义类型,自定义类型需要实现Value接口(要求receiver是指针)

flag.Var(&flagVal, "name", "help message for flagname")

注:这种方式是没有提供默认值的,所以默认值就是类型的零值。(备注:具体如何实现自定义类型绑定会在后面介绍)

3. 命令行参数解析及语法格式

命令行参数定义好了,该如何解析呢?

3.1. 解析

在所有flag定义完成之后,调用 flag.Parse() 解析

  • 对该函数的调用必须在所有命令参数存储载体的声明(即:上面对 num 和 port的声明)和设置(即: 上面调用 flag.IntVar()) 之后,并且在读取任何命令参数值之前进行。正因为如此,我们最好把 flag.Parse() 放在 main 函数第一行.
  • 解析函数将会在碰到第一个 非 flag 命令行参数 时停止,非flag命令行参数是指不满足命令行语法的参数,如命令行参数为 cmd –flag=true abc 则第一个非flag 命令行参数为 “abc”

注:调用 Parse 解析后,就可以直接使用 flag 本身(指针类型)或者绑定的变量了(值类型), 还可通过 flag.Args(), flag.Arg(i) 来获取非flag命令行参数

3.2. 命令行语法

命令行语法格式:

-flag       // 只支持bool类型

-flag xxx   // 使用空格,一个 - 符号
--flag xxx  // 使用空格,两个 - 符号

-flag=xxx   // 使用等号,一个 - 符号
--flag=xxx  // 使用等号,两个 - 符号

注:布尔类型的参数防止解析时的二义性,应该使用等号的方式指定。

4. 变量、类型和函数

4.1. 变量

  • flag.ErrHelp:该错误类型用于当命令行指定了 –help 参数但没有定义时。

  • flag.Usage:这是一个函数,用于输出所有定义了的命令行参数和帮助信息(Usage Message)。 当命令行参数解析出错,该函数会被调用。我们可通过 flag.Usage = func() {} 指定自定义的Usage。

    • flag.Usage 的类型是func(),即一种无参数声明且无结果声明的函数类型

注意,对flag.Usage的赋值必须在调用 flag.Parse() 函数之前

  • flag.CommandLine: 类型 flag.FlagSet 的实例, 相当于默认情况下的命令参数容器.

4.2. 函数

4.3. 类型

4.3.1. ErrorHandling

type ErrorHandling int 

该类型定义了在参数解析出错时,处理错误定义的三个常量

const (
    ContinueOnError ErrorHandling = iota
    ExitOnError
    PanicOnError
)

4.3.2. FlagSet

FlagSet 是该包的核心结构

// A FlagSet represents a set of defined flags.
type FlagSet struct {
    // Usage is the function called when an error occurs while parsing flags.
    // The field is a function (not a method) that may be changed to point to
    // a custom error handler.
    Usage func()
    name          string            // FlagSet的名字。commandLine 给的是os.Args[0]
    parsed        bool              // 是否执行过Parse()
    actual        map[string]*Flag  // 存放实际传递了的参数(即命令行参数)
    formal        map[string]*Flag  // 存放所有已定义命令行参数
    args          []string          // arguments after flags 存放非标志的参数列表(即标志后面的参数)
    exitOnError  bool               // does the program exit if there's an error?
    errorHandling ErrorHandling     // 当解析出错时,处理错误的方式
    output        io.Writer         // nil means stderr; use out() accessor
}

该类型同时提供了一系列的方法集合 <MethodSet>, 通过这些方式来实现CLI的灵活处理

4.3.3. 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
}

4.3.4. Value

Value定义:

type Value interface {
  String() string
  Set(string) error
}

4.4. 主要类型的方法

4.4.1. 实例化方式:flag.NewFlagSet()

flag.NewFlagSet() 用于实例化 FlagSet。例如:预定义的 FlagSet实例 subCommand 的定义方式:

subCommand := flag.NewFlagSet("push", ExitOnError)
subCommand.Parse(os.Args[1:])   // os.Args[1:] 指的就是我们给定的那些命令参数

注:可用于定义子命令

  • 由于FlagSet的字段没有 export,其他方式获得FlagSet的实例后,比如 FlagSet{}、new(FlagSet), 应该调用 Init() 方法初始化 name和errorHandling
  • 这样做的好处依然是更灵活地定制命令参数容器。但更重要的是,你的定制完全不会影响到那个全局变量flag.CommandLine

4.4.2. 解析参数:flag.Parse()

该方法应该在flag参数定义后, 具体参数值被访问之前调用。

4.4.2.1. 解析停止

  • 第一个non-flag参数

    当遇到单独的一个”-”或不是”-”开始时,会停止解析

  • 两个连续的 ‘-’

5. 将 flag 绑定用户自定义类型

首先,用户自定义类型需要重新实现 flag.Value 接口

flag.Value 接口定义如下:

type Value interface{
    String() string
    Set(string) error
}

Note:

  • String(): 方法输出结果为一个字符串值
  • Set(string) error: 方法定义了如何解析flag参数指定的值

例如:解析数据库集群服务器的地址到一个 slice 中 ?

5.1. 重写 flag.Value

  • 自定义flag参数值接收类型
type addrs []string
  • 实现 String() string 方法
func(i *addrs) String() string {
    return fmt.Sprintf("%v", *i)
}
  • 实现 Set(string) error 方法
func (i *addrs) Set(value string) error {
    *i = append(*i, value)
    return nil
}

5.2. 解析

var DBAdds addrs

func init() {
    flag.Var(&DBAdds, "db_addr", "database cluster server address")
    flag.Parse()
} 

func main() {
    fmt.Println(DBAdds)
}
$ ./app -db_addr 192.168.1.100 -db_addr 192.168.1.101 -db_addr 192.168.1.102

# output: 

完整代码:

package main

import (
    "fmt"
    "flag"
)

// 自定义flag参数值接收类型
type addrs []string

// 实现 String() string 方法
func(i *addrs) String() string {
    return fmt.Sprintf("%v", *i)
}

// 实现 Set(string) error 方法
func (i *addrs) Set(value string) error {
    *i = append(*i, value)
    return nil
}

// DBAdds 声明接收命令行参数的变量
var DBAdds addrs

// 定义 flag 并完成解析
func init() {
    flag.Var(&DBAdds, "db_addr", "database cluster server address")
    flag.Parse()
} 

func main() {
    fmt.Println(DBAdds)
}

6. See Also

Thanks to the authors 🙂


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK