解析命令行参数
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。
有疑问加站长微信联系(非本文作者)