Go语言flag库

1、Go语言flag库

1.1 简介

flag 用于解析命令行选项,有过类 Unix 系统使用经验的童鞋对命令行选项应该不陌生,例如命令 ls -al 列出

当前目录下所有文件和目录的详细信息,其中 -al 就是命令行选项。

命令行选项在实际开发中很常用,特别是在写工具的时候。

  • 指定配置文件的路径,如 redis-server ./redis.conf 以当前目录下的配置文件 redis.conf 启动 Redis

    服务器;

  • 自定义某些参数,如 python -m SimpleHTTPServer 8080 启动一个 HTTP 服务器,监听 8080 端口。如果

    不指定,则默认监听 8000 端口。

1.2 快速使用

学习一个库的第一步当然是使用它。我们先看看 flag 库的基本使用:

package main

import (
	"flag"
	"fmt"
)

var (
	intflag    int
	boolflag   bool
	stringflag string
)

func init() {
	flag.IntVar(&intflag, "intflag", 0, "int flag value")
	flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
	flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
	flag.Parse()
	fmt.Println("int flag:", intflag)
	fmt.Println("bool flag:", boolflag)
	fmt.Println("string flag:", stringflag)
}
# 输出结果
$ go run 001.go
int flag: 0
bool flag: false
string flag: default

如果不设置某个选项,相应变量会取默认值。

可以看到没有设置的选项 stringflag 为默认值 default。

添加命令行参数将命令行中的其它选项传给这个程序。

# 输出结果
# -boolflag不需要传值,加上该参数值为true,不加该参数值为默认值
$ go run 001.go -intflag 12 -boolflag -stringflag test
int flag: 12
bool flag: true
string flag: test

$ go run 001.go -intflag 12 -stringflag test
int flag: 12
bool flag: false
string flag: test

可以使用 -h 显示选项帮助信息:

$ go run 001.go -h
Usage of C:\Users\admin\AppData\Local\Temp\go-build2530652645\b001\exe\001.exe:
  -boolflag
        bool flag value
  -intflag int
        int flag value
  -stringflag string
        string flag value (default "default")

总结一下,使用 flag 库的一般步骤:

  • 定义一些全局变量存储选项的值,如这里的 intflag/boolflag/stringflag;

  • 在 init 方法中使用 flag.TypeVar 方法定义选项,这里的 Type 可以为基本类型 Int/Uint/Float64/Bool,

    还可以是时间间隔 time.Duration。定义时传入变量的地址、选项名、默认值和帮助信息;

  • 在 main 方法中调用 flag.Parse 从 os.Args[1:] 中解析选项。因为 os.Args[0] 为可执行程序路径,会被剔

    除。

注意点:

flag.Parse 方法必须在所有选项都定义之后调用,且 flag.Parse 调用之后不能再定义选项。如果按照前面的步

骤,基本不会出现问题。 因为 init 在所有代码之前执行,将选项定义都放在 init 中, main 函数中执行

flag.Parse 时所有选项都已经定义了。

1.3 选项格式

flag 库支持三种命令行选项格式。

-flag
-flag=x
-flag x

--- 都可以使用,它们的作用是一样的。有些库使用 - 表示短选项,-- 表示长选项。相对而言,flag 使用

起来更简单。

第一种形式只支持布尔类型的选项,出现即为 true,不出现为默认值。 第三种形式不支持布尔类型的选项,因为

这种形式的布尔选项在类 Unix 系统中可能会出现意想不到的行为。看下面的命令:

cmd -x *

其中,* 是 shell 通配符。如果有名字为 0、false的文件,布尔选项 -x 将会取 false。反之,布尔选项 -x 将会取

true,而且这个选项消耗了一个参数。 如果要显示设置一个布尔选项为 false,只能使用 -flag=false 这种形式。

遇到第一个非选项参数(即不是以---开头的)或终止符--,解析停止。运行下面程序:

# 输出结果
$ go run 001.go noflag -intflag 12
int flag: 0
bool flag: false
string flag: default

因为解析遇到 noflag 就停止了,后面的选项-intflag 没有被解析到。所以所有选项都取的默认值。

运行下面的程序:

# 输出结果
$ go run 001.go -intflag 12 -- -boolflag=true
int flag: 12
bool flag: false
string flag: default

首先解析了选项 intflag,设置其值为 12。遇到 -- 后解析终止了,后面的 --boolflag=true 没有被解析到,所

以 boolflag 选项取默认值 false。

解析终止之后如果还有命令行参数,flag 库会存储下来,通过 flag.Args 方法返回这些参数的切片。 可以通过

flag.NArg 方法获取未解析的参数数量,flag.Arg(i) 访问位置 i(从 0 开始)上的参数。 选项个数也可以通过调用

flag.NFlag 方法获取。

稍稍修改一下上面的程序:

package main

import (
	"flag"
	"fmt"
)

var (
	intflag    int
	boolflag   bool
	stringflag string
)

func init() {
	flag.IntVar(&intflag, "intflag", 0, "int flag value")
	flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
	flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
	flag.Parse()
	// [-stringflag test]
	fmt.Println(flag.Args())
	// Non-Flag Argument Count: 2
	fmt.Println("Non-Flag Argument Count:", flag.NArg())
	// Argument 0: -stringflag
	// Argument 1: test
	for i := 0; i < flag.NArg(); i++ {
		fmt.Printf("Argument %d: %s\n", i, flag.Arg(i))
	}
	// Flag Count: 1
	fmt.Println("Flag Count:", flag.NFlag())
}
# 输出结果
$ go run 002.go -intflag 12 -- -stringflag test
[-stringflag test]
2
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
Flag Count: 1

解析遇到--终止后,剩余参数 -stringflag test 保存在 flag 中,可以通过 Args/NArg/Arg 等方法访问。

整数选项值可以接受 1234(十进制)、0664(八进制)和 0x1234(十六进制)的形式,并且可以是负数。实际

上 flag 在内部使用 strconv.ParseInt 方法将字符串解析成 int。 所以理论上,ParseInt 接受的格式都可以。

布尔类型的选项值可以为:

  • 取值为 true 的:1、t、T、true、TRUE、True;

  • 取值为 false 的:0、f、F、false、FALSE、False。

1.4 另一种定义选项的方式

上面我们介绍了使用 flag.TypeVar 定义选项,这种方式需要我们先定义变量,然后变量的地址。 还有一种方式,

调用 flag.Type(其中 Type 可以为 Int/Uint/Bool/Float64/String/Duration 等)会自动为我们分配变量,返回该

变量的地址。用法与前一种方式类似:

package main

import (
	"flag"
	"fmt"
)

var (
	intflag    *int
	boolflag   *bool
	stringflag *string
)

func init() {
	intflag = flag.Int("intflag", 0, "int flag value")
	boolflag = flag.Bool("boolflag", false, "bool flag value")
	stringflag = flag.String("stringflag", "default", "string flag value")
}

func main() {
	flag.Parse()
	fmt.Println("int flag:", *intflag)
	fmt.Println("bool flag:", *boolflag)
	fmt.Println("string flag:", *stringflag)
}
# 输出结果
$ go run 003.go
int flag: 0
bool flag: false
string flag: default
# 输出结果
$ go run 003.go -intflag 12
int flag: 12
bool flag: false
string flag: default

除了使用时需要解引用,其它与前一种方式基本相同。

1.5 高级用法

1.5.1 定义短选项

flag 库并没有显示支持短选项,但是可以通过给某个相同的变量设置不同的选项来实现。即两个选项共享同一个

变量。 由于初始化顺序不确定,必须保证它们拥有相同的默认值。否则不传该选项时,行为是不确定的。

package main

import (
	"flag"
	"fmt"
)

var logLevel string

func init() {
	const (
		defaultLogLevel = "DEBUG"
		usage           = "set log level value"
	)
	flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage)
	flag.StringVar(&logLevel, "l", defaultLogLevel, usage+"(shorthand)")
}

func main() {
	flag.Parse()
	fmt.Println("log level:", logLevel)
}
# 输出结果
$ go run 004.go
log level: DEBUG
# 输出结果
$ go run 004.go -log_type WARNING
log level: WARNING
# 输出结果
$ go run 004.go -l WARNING
log level: WARNING
1.5.2 解析时间间隔

除了能使用基本类型作为选项,flag 库还支持 time.Duration 类型,即时间间隔。时间间隔支持的格式非常之

多,例如 300ms-1.5h2h45m 等等等等。 时间单位可以是 ns/us/ms/s/m/h/day 等。实际上 flag 内部会

调用 time.ParseDuration。

具体支持的格式可以参见 time 库的文档:https://pkg.go.dev/time

package main

import (
	"flag"
	"fmt"
	"time"
)

var (
	period time.Duration
)

func init() {
	flag.DurationVar(&period, "period", 1*time.Second, "sleep period")
}

func main() {
	flag.Parse()
	fmt.Printf("Sleeping for %v...", period)
	time.Sleep(period)
	fmt.Println()
}
# 输出结果
$ go run 005.go
Sleeping for 1s...
# 输出结果
$ go run 005.go -period 1m30s
Sleeping for 1m30s...

根据传入的命令行选项 period,程序睡眠相应的时间,默认 1 秒。

1.5.3 自定义选项

除了使用 flag 库提供的选项类型,我们还可以自定义选项类型。我们分析一下标准库中提供的案例:

package main

import (
	"errors"
	"flag"
	"fmt"
	"strings"
	"time"
)

type interval []time.Duration

func (i *interval) String() string {
	return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
	if len(*i) > 0 {
		return errors.New("interval flag already set")
	}
	for _, dt := range strings.Split(value, ",") {
		duration, err := time.ParseDuration(dt)
		if err != nil {
			return err
		}
		*i = append(*i, duration)
	}
	return nil
}

var (
	intervalFlag interval
)

func init() {
	flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events")
}

func main() {
	flag.Parse()
	fmt.Println(intervalFlag)
}

首先定义一个新类型,这里定义类型 interval 。

新类型必须实现 flag.Value 接口:

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

其中 String 方法格式化该类型的值,flag.Parse 方法在执行时遇到自定义类型的选项会将选项值作为参数调用

该类型变量的 Set 方法。 这里将以 , 分隔的时间间隔解析出来存入一个切片中。

自定义类型选项的定义必须使用 flag.Var 方法。

编译、执行程序:

# 程序输出
$ go run 006.go
[]
# 程序输出
$ go run 006.go -deltaT 10s
[10s]
# 程序输出
$ go run 006.go -deltaT 30s,1m,1m30s
[30s 1m0s 1m30s]

如果指定的选项值非法,Set 方法返回一个 error 类型的值,Parse 执行终止,打印错误和使用帮助。

1.5.4 解析程序中的字符串

有时候选项并不是通过命令行传递的。例如,从配置表中读取或程序生成的。这时候可以使用 flag.FlagSet 结构

的相关方法来解析这些选项。

实际上,我们前面调用的 flag 库的方法,都会间接调用 FlagSet 结构的方法。flag 库中定义了一个 FlagSet 类型的

全局变量 CommandLine 专门用于解析命令行选项。 前面调用的 flag 库的方法只是为了提供便利,它们内部都是

调用的 CommandLine 的相应方法。

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

func Parse() {
  CommandLine.Parse(os.Args[1:])
}

func IntVar(p *int, name string, value int, usage string) {
  CommandLine.Var(newIntValue(value, p), name, usage)
}

func Int(name string, value int, usage string) *int {
  return CommandLine.Int(name, value, usage)
}

func NFlag() int { return len(CommandLine.actual) }

func Arg(i int) string {
  return CommandLine.Arg(i)
}

func NArg() int { return len(CommandLine.args) }

同样的,我们也可以自己创建 FlagSet 类型变量来解析选项。

package main

import (
	"flag"
	"fmt"
)

func main() {
	args := []string{"-intflag", "12", "-stringflag", "test"}

	var intflag int
	var boolflag bool
	var stringflag string

	fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError)
	fs.IntVar(&intflag, "intflag", 0, "int flag value")
	fs.BoolVar(&boolflag, "boolflag", false, "bool flag value")
	fs.StringVar(&stringflag, "stringflag", "default", "string flag value")

	fs.Parse(args)

	fmt.Println("int flag:", intflag)
	fmt.Println("bool flag:", boolflag)
	fmt.Println("string flag:", stringflag)
}
# 输出结果
$ go run 007.go
int flag: 12
bool flag: false
string flag: test

NewFlagSet 方法有两个参数,第一个参数是程序名称,输出帮助或出错时会显示该信息。第二个参数是解析出错

时如何处理,有几个选项:

  • ContinueOnError:发生错误后继续解析,CommandLine 就是使用这个选项;

  • ExitOnError:出错时调用 os.Exit(2) 退出程序;

  • PanicOnError:出错时产生 panic。

与直接使用 flag 库的方法有一点不同,FlagSet 调用 Parse 方法时需要显示传入字符串切片作为参数。因为

flag.Parse 在内部调用了 CommandLine.Parse(os.Args[1:])。

1.6 参考

flag库文档:https://pkg.go.dev/flag

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值