命令行参数的解析:Flag 库详解

系统
如果真的遇上了所谓的复杂场景,那么还可以使用 Golang 的标准库 flag 包来处理命令行参数。本文将介绍 Golang 标准库中 flag 包的用法。

 [[375481]]

在 Golang 程序中有很多种方法来处理命令行参数。

简单的情况下可以不使用任何库,直接使用 os.Args

package main 
 
import ( 
    "fmt" 
    "os" 

 
func main() { 
    if len(os.Args) > 0 { 
        for index, arg := range os.Args { 
            fmt.Printf("args[%d]=%v\n"index, arg) 
        } 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

试着运行一下,第一个参数是执行文件的路径。

$ go run demo.go hello world hello golang 
args[0]=/var/folders/72/lkr7ltfd27lcf36d75jdyjr40000gp/T/go-build187785213/b001/exe/demo 
args[1]=hello 
args[2]=world 
args[3]=hello 
args[4]=golang 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

从上面你可以看到,os.Args 只能处理简单的参数,而且对于参数的位置有严格的要求。对于一些比较复杂的场景,就需要你自己定义解析规则,非常麻烦。

如果真的遇上了所谓的复杂场景,那么还可以使用 Golang 的标准库 flag 包来处理命令行参数。

本文将介绍 Golang 标准库中 flag 包的用法。

1. 参数种类

根据参数是否为布尔型,可以分为两种:

  • 布尔型参数:如 --debug,后面不用再接具体的值,指定就为 True,不指定就为 False非布尔型参数
  • 非布尔型参数:非布尔型,有可能是int,string 等其他类型,如 --name jack ,后面可以接具体的参数值

根据参数名的长短,还可以分为:

  • 长参数:比如 --name jack 就是一个长参数,参数名前有两个 -
  • 短参数:通常为一个或两个字母(是对应长参数的简写),比如 -n ,参数名前只有一个 -

2. 入门示例

我先用一个字符串类型的参数的示例,抛砖引玉

package main 
 
import ( 
    "flag" 
    "fmt" 

 
func main(){ 
    var name string 
    flag.StringVar(&name"name""jack""your name"
 
flag.Parse()  // 解析参数 
    fmt.Println(name

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

flag.StringVar 定义了一个字符串参数,它接收几个参数

  • 第一个参数 :接收值后,存放在哪个变量里,需为指针
  • 第二个参数 :在命令行中使用的参数名,比如 --name jack 里的 name
  • 第三个参数 :若命令行中未指定该参数值,那么默认值为 jack
  • 第四个参数:记录这个参数的用途或意义

运行以上程序,输出如下

$ go run demo.go 
jack 
 
$ go run demo.go --name wangbm 
wangbm 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

3. 改进一下

如果你的程序只接收很少的几个参数时,上面那样写也没有什么问题。

但一旦参数数量多了以后,一大堆参数解析的代码堆积在 main 函数里,影响代码的可读性、美观性。

建议将参数解析的代码放入 init 函数中,init 函数会先于 main 函数执行。

package main 
 
import ( 
    "flag" 
    "fmt" 

 
var name string 
 
func init()  { 
    flag.StringVar(&name"name""jack""your name"

 
func main(){ 
    flag.Parse() 
    fmt.Println(name

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

4. 参数类型

当你在命令行中指定了参数,Go 如何解析这个参数,转化成何种类型,是需要你事先定义的。

不同的参数,对应着 flag 中不同的方法。

下面分别讲讲不同的参数类型,都该如何定义。

布尔型

实现效果:当不指定 --debug 时,debug 的默认值为 false,你一指定 --debug,debug 为赋值为 true。

var debug bool 
 
func init()  { 
    flag.BoolVar(&debug, "debug"false"是否开启 DEBUG 模式"

 
func main(){ 
    flag.Parse() 
    fmt.Println(debug) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

运行后,执行结果如下

$ go run main.go  
false 
$ go run main.go --debug 
true 
  • 1.
  • 2.
  • 3.
  • 4.

数值型

定义一个 age 参数,不指定默认为 18

var age int 
 
func init()  { 
    flag.IntVar(&age, "age", 18, "你的年龄"

 
func main(){ 
    flag.Parse() 
    fmt.Println(age) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

运行后,执行结果如下

$ go run main.go  
18 
 
$ go run main.go --age 20 
20 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

int64、 uint 和 float64 类型分别对应 Int64Var 、 UintVar、Float64Var 方法,也是同理,不再赘述。

字符串

定义一个 name参数,不指定默认为 jack

var name string 
 
func init()  { 
    flag.StringVar(&name"name""jack""你的名字"

 
func main(){ 
    flag.Parse() 
    fmt.Println(name

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

运行后,执行结果如下

$ go run main.go  
jack 
 
$ go run main.go --name wangbm 
wangbm 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

时间类型

定义一个 interval 参数,不指定默认为 1s

var interval time.Duration 
 
func init()  { 
    flag.DurationVar(&interval, "interval", 1 * time.Second"循环间隔"

 
func main(){ 
    flag.Parse() 
    fmt.Println(interval) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

验证效果如下

$ go run main.go  
1s 
 
$ go run main.go --interval 2s 
2s 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

5. 自定义类型

flag 包支持的类型有 Bool、Duration、Float64、Int、Int64、String、Uint、Uint64。

这些类型的参数被封装到其对应的后端类型中,比如 Int 类型的参数被封装为 intValue,String 类型的参数被封装为 stringValue。

这些后端的类型都实现了 flag.Value 接口,因此可以把一个命令行参数抽象为一个 Flag 类型的实例。下面是 Value 接口和 Flag 类型的代码:

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

 
// Flag 类型 
type Flag struct { 
    Name     string // name as it appears on command line 
    Usage    string // help message 
    Value    Value  // value as set 是个 interface,因此可以是不同类型的实例。 
    DefValue string // default value (as text); for usage message 

 
func Var(value Value, name string, usage string) { 
    CommandLine.Var(value, name, usage) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

想要实现自定义类型的参数,其实只要 Var 函数的第一个参数对象实现 flag.Value接口即可

type sliceValue []string 
 
 
func newSliceValue(vals []string, p *[]string) *sliceValue { 
    *p = vals 
    return (*sliceValue)(p) 

 
func (s *sliceValue) Set(val string) error { 
         // 如何解析参数值 
    *s = sliceValue(strings.Split(val, ",")) 
    return nil 

 
func (s *sliceValue) String() string { 
    return strings.Join([]string(*s), ","

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

比如我想实现如下效果,传入的参数是一个字符串,以逗号分隔,flag 的解析时将其转成 slice。

$ go run demo.go -members "Jack,Tom" 
[Jack Tom] 
  • 1.
  • 2.

那我可以这样子编写代码

var members []string 
type sliceValue []string 
 
 
func newSliceValue(vals []string, p *[]string) *sliceValue { 
    *p = vals 
    return (*sliceValue)(p) 

 
func (s *sliceValue) Set(val string) error { 
         // 如何解析参数值 
    *s = sliceValue(strings.Split(val, ",")) 
    return nil 

 
 
func (s *sliceValue) String() string { 
    return strings.Join([]string(*s), ","

 
func init()  { 
    flag.Var(newSliceValue([]string{}, &members), "members""会员列表"

 
func main(){ 
    flag.Parse() 
    fmt.Println(members) 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

有的朋友 可能会对 (*sliceValue)(p) 这行代码有所疑问,这是什么意思呢?

关于这个,其实之前在 【2.9 详细图解:静态类型与动态类型】有讲过,忘记了可以前往复习。

6. 长短选项

flag 包,在使用上,其实并没有没有长短选项之别,你可以看下面这个例子

package main 
 
import ( 
    "flag" 
    "fmt" 

 
var name string 
 
func init()  { 
    flag.StringVar(&name"name""明哥""你的名字"

 
func main(){ 
    flag.Parse() 
    fmt.Println(name

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

通过指定如下几种参数形式

$ go run main.go  
明哥 
$ go run main.go --name jack 
jack 
$ go run main.go -name jack 
jack 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

一个 - 和两个 - 执行结果是相同的。

那么再加一个呢?

终于报错了。说明最多只能指定两个 -

$ go run main.go ---name jack 
bad flag syntax: ---name 
Usage of /tmp/go-build245956022/b001/exe/main: 
  -name string 
        你的名字 (default "明哥"
exit status 2 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

7. 总结一下

flag 在绝大多数场景下,它是够用的,但如果要支持更多的命令传入格式,flag 可能并不是最好的选择。

那些在标准库不能解决的场景,往往会有相应的Go爱好者提供第三方解决方案。我所了解到的 cobra 就是一个非常不错的库。

 本文转载自微信公众号「Go编程时光」,可以通过以下二维码关注。转载本文请联系Go编程时光公众号。

 

责任编辑:武晓燕 来源: Go编程时光
相关推荐

2023-07-05 08:38:48

GolangGo语言

2021-08-10 21:58:54

Go语言Flag库

2021-11-15 14:30:49

Pythonargparse编程语言

2017-03-08 11:10:39

LinuxShell命令

2021-11-08 10:45:07

Python命令工具

2010-03-10 17:23:37

Python 命令行参

2010-07-15 10:47:22

Perl命令行

2009-12-24 14:51:39

Linux命令行

2010-11-24 15:33:59

mysql命令行参数

2010-08-20 10:05:23

用户命令

2009-07-20 09:55:30

华为命令行解析华为认证

2024-05-15 17:05:16

GoLangflagcast

2011-08-22 11:51:13

Linuxconfigure

2017-05-25 10:32:40

命令linux系统

2010-07-26 09:32:41

Perl命令行

2010-07-20 14:02:38

Perl命令行参数

2010-07-15 11:08:23

Perl命令行

2024-04-29 07:38:20

PythonDocopt开发

2009-08-11 09:58:22

Linux命令行Linux命令svn命令

2017-06-15 10:32:56

OracleDBV命令行工具
点赞
收藏

51CTO技术栈公众号