Go36-2-flag包

解析命令行参数

Go语言标准库中的flag包专门用于接收和解析命令参数。

基本用法

从命令行接收参数并打印出来:

package main

import (
    "flag"
    "fmt"
)

var name string

func init() {
    flag.StringVar(&name, "name", "Nobody", "请设置名字")
}

func main() {
    flag.Parse()
    fmt.Printf("Hello, %s!\n", name)
}

函数flag.StringVar接受4个参数:

  • 第1个参数,是用于存储该命令参数的值的地址,就是示例中声明的变量name的地址,由表达式&name表示
  • 第2个参数,是为了指定该命令参数的名称,这里是name
  • 第3个参数,是为了指定在未追加该命令参数时的默认值,这里是Nobody
  • 第4个参数,是该命令参数的简短说明,这在打印命令说明时会用到

还有一个函数flag.String和flag.StringVar类似。就是没有第一个参数了,而是直接返回一个已经分配好的用于存储命令参数值的地址。

package main

import (
    "flag"
    "fmt"
)

var name string

func init() {
    name = *flag.String("name", "Nobody", "请设置名字")
}

func main() {
    flag.Parse()
    fmt.Printf("Hello, %s!\n", name)
}

函数调用顺序

函数flag.Parse用于真正解析命令参数,并把它们的值赋给相应的变量。
该函数的调用必须在所有命令参数的声明和设置之后,并且在读取任何命令参数值之前。
也就是像例子里做的,把参数设置的语句放在init函数里先执行,然后把flag.Parse放在main函数的开头,在调用使用命令行参数的语句之前。

自定制参数使用说明

在上面的例子中,执行的时候带上-h或者--help参数,就能看到如下的使用说明:

PS G:\Steed\Documents\Go\src\Go36\article02\example01> go run main.go --help
Usage of C:\Users\Steed\AppData\Local\Temp\go-build897600374\command-line-arguments\_obj\exe\main.exe:
  -name string
        请设置名字 (default "Nobody")
exit status 2
PS G:\Steed\Documents\Go\src\Go36\article02\example01>

输出的第一行在Usage of后面一长串的路径,是go run命令构建上述命令源码文件时临时生成的可执行文件的完整路径。如果是编译之后再执行,就是可执行文件的相对路径,就没那么难看了。
并且这一行的说明内容是可以自定制的,接下来就是通过包提供的方法对这行说明进行自定制。这3种方法是一层一层更加接近底层的调用的。

通过flag.Usage定制

最简单的一种定制方式就是对变量flag.Usage重新赋值。为该变量定义一个函数,在要打印说明的时候,其实就是调用执行了这个方法:

package main

import (
    "flag"
    "fmt"
    "os"
)

var name string

func init() {
    // 下面2句语句的顺序随意
    flag.StringVar(&name, "name", "Nobody", "请设置名字")
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage of %s\n", "Say Hello")
        flag.PrintDefaults()
    }
}

func main() {
    flag.Parse()
    fmt.Printf("Hello, %s!\n", name)
}

flag.Usage的赋值必须在调用flag.Parse()之前。

通过flag.CommandLine定制

在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
flag.CommandLine相当于默认情况下的命令参数容器。通过对flag.CommandLine重新赋值,就可以更深层次地定制当前命令源码文件的参数使用说明。
仅修改之前的init函数部分,去掉flag.Usage的赋值语句,改为通过对flag.CommandLine赋值来进行定制:

func init() {
    // flag.CommandLine对象的创建必须在前面执行
    flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
    //flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)
    flag.CommandLine.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage of %s\n", "Say Hello")
        flag.PrintDefaults()
    }
    flag.StringVar(&name, "name", "Nobody", "请设置名字")
}

这样修改后的效果和之前完全一致。这里主要出分离出了下面这句:

flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)

这句的第一个参数应该是没有用的了,这里传入空字符串。
第二个参数可以是一下的3个常量:

const (
    ContinueOnError ErrorHandling = iota // Return a descriptive error.
    ExitOnError                          // Call os.Exit(2).
    PanicOnError                         // Call panic with a descriptive error.
)

定义在解析遇到问题后,是执行何种操作。默认的就是ExitOnError,所以在--help执行打印说明后,最后一行会出现“exit status 2”,以状态码2退出。
这里可以根据需要定制为抛出Panic。

创建私有命令参数容器

这里的代码上上面的例子差不多,依然是调用flag.NewFlagSet()创建命令参数容器。不过这次把容器赋值给自定义的变量:

package main

import (
    "flag"
    "fmt"
    "os"
)

var name string
var cmdLine = flag.NewFlagSet("cmdLine Say Hello", flag.ExitOnError)

func init() {
    cmdLine.StringVar(&name, "name", "Nobody", "请设置名字")
}

func main() {
    cmdLine.Parse(os.Args[1:])
    fmt.Printf("Hello, %s!\n", name)
}

首先通过命令 var cmdLine = flag.NewFlagSet("cmdLine Say Hello", flag.ExitOnError) 创建了私有的命令参数容器。
然后,之后其他所有方法的调用都通过这个变量来调用的。
上面这样做之后,就完全脱离了flag.CommandLine。而是使用 *flag.FlagSet 类型的变量 cmdLine 进行各种调用了。该类型拥有很多方法,可以继续探索。
主要是可以更灵活地定制命令参数容器。并且你的定制完全不会影响到全局变量flag.CommandLine。

猜你喜欢

转载自blog.51cto.com/steed/2337596