1.  
  2. 主页
  3.  / 
  4. Go 每日一库
  5.  / 
  6. Go 每日一库之 flag 使用详解

Go 每日一库之 flag 使用详解

简介

flag用于解析命令行选项。有过类 Unix 系统使用经验的童鞋对命令行选项应该不陌生。例如命令ls -al列出当前目录下所有文件和目录的详细信息,其中-al就是命令行选项。

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

  • 指定配置文件的路径,如redis-server ./redis.conf以当前目录下的配置文件redis.conf启动 Redis 服务器;
  • 自定义某些参数,如python -m SimpleHTTPServer 8080启动一个 HTTP 服务器,监听 8080 端口。如果不指定,则默认监听 8000 端口。

快速使用

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

可以先编译程序,然后运行(我使用的是 Win10 + Git Bash):

输出:

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

输出:

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

还可以直接使用go run,这个命令会先编译程序生成可执行文件,然后执行该文件,将命令行中的其它选项传给这个程序。

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

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

  • 定义一些全局变量存储选项的值,如这里的intflag/boolflag/stringflag
  • init方法中使用flag.TypeVar方法定义选项,这里的Type可以为基本类型Int/Uint/Float64/Bool,还可以是时间间隔time.Duration。定义时传入变量的地址、选项名、默认值和帮助信息;
  • main方法中调用flag.Parseos.Args[1:]中解析选项。因为os.Args[0]为可执行程序路径,会被剔除。

注意点:

flag.Parse方法必须在所有选项都定义之后调用,且flag.Parse调用之后不能再定义选项。如果按照前面的步骤,基本不会出现问题。 因为init在所有代码之前执行,将选项定义都放在init中,main函数中执行flag.Parse时所有选项都已经定义了。

选项格式

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

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

第一种形式只支持布尔类型的选项,出现即为true,不出现为默认值。 第三种形式不支持布尔类型的选项。因为这种形式的布尔选项在类 Unix 系统中可能会出现意想不到的行为。看下面的命令:

其中,*是 shell 通配符。如果有名字为 0、false的文件,布尔选项-x将会取false。反之,布尔选项-x将会取true。而且这个选项消耗了一个参数。 如果要显示设置一个布尔选项为false,只能使用-flag=false这种形式。

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

将会输出:

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

运行下面的程序:

将会输出:

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

解析终止之后如果还有命令行参数,flag库会存储下来,通过flag.Args方法返回这些参数的切片。 可以通过flag.NArg方法获取未解析的参数数量,flag.Arg(i)访问位置i(从 0 开始)上的参数。 选项个数也可以通过调用flag.NFlag方法获取。

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

编译运行该程序:

输出:

解析遇到--终止后,剩余参数-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。

另一种定义选项的方式

上面我们介绍了使用flag.TypeVar定义选项,这种方式需要我们先定义变量,然后变量的地址。 还有一种方式,调用flag.Type(其中Type可以为Int/Uint/Bool/Float64/String/Duration等)会自动为我们分配变量,返回该变量的地址。用法与前一种方式类似:

编译并运行程序:

将输出:

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

高级用法

定义短选项

flag库并没有显示支持短选项,但是可以通过给某个相同的变量设置不同的选项来实现。即两个选项共享同一个变量。 由于初始化顺序不确定,必须保证它们拥有相同的默认值。否则不传该选项时,行为是不确定的。

编译、运行程序:

使用长、短选项均输出:

不传入该选项,输出默认值:

解析时间间隔

除了能使用基本类型作为选项,flag库还支持time.Duration类型,即时间间隔。时间间隔支持的格式非常之多,例如”300ms”、”-1.5h”、“2h45m”等等等等。 时间单位可以是 ns/us/ms/s/m/h/day 等。实际上flag内部会调用time.ParseDuration。具体支持的格式可以参见time(需fq)库的文档。

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

自定义选项

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

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

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

其中String方法格式化该类型的值,flag.Parse方法在执行时遇到自定义类型的选项会将选项值作为参数调用该类型变量的Set方法。 这里将以,分隔的时间间隔解析出来存入一个切片中。

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

编译、执行程序:

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

解析程序中的字符串

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

实际上,我们前面调用的flag库的方法,都会间接调用FlagSet结构的方法。flag库中定义了一个FlagSet类型的全局变量CommandLine专门用于解析命令行选项。 前面调用的flag库的方法只是为了提供便利,它们内部都是调用的CommandLine的相应方法:

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

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

  • ContinueOnError:发生错误后继续解析,CommandLine就是使用这个选项;
  • ExitOnError:出错时调用os.Exit(2)退出程序;
  • PanicOnError:出错时产生 panic。

随便看一眼flag库中的相关代码:

与直接使用flag库的方法有一点不同,FlagSet调用Parse方法时需要显示传入字符串切片作为参数。因为flag.Parse在内部调用了CommandLine.Parse(os.Args[1:])

示例代码都放在GitHub上了。

这篇文章对您有用吗?

我们要如何帮助您?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注