GO语言的flag包:
flag的定义:
flag.String(), Bool(), Int() //这里这是列举了几个
然后就是两种定义的方式:
var ip = flag.Int("flagname", 1234, "help message for flagname") //ip 为指针类型,Int或者
String返回的都是指针类型
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")//此处需要在外面手动定义flagvar
第一种方式是返回一个值,第二种方式是把变量的传进去。
通过flag.Var()绑定自定义类型,自定义类型需要实现Value接口(Receives必须为指针),如:
flag.Var(&flagVal, "name", "help message for flagname")
对于这种类型的flag,默认值为该变量类型的初始值
解析命令行参数
Go语言标准库中的flag包专门用于接收和解析命令参数。
flag的解析
flag.Parse()
解析函数将会在碰到第一个非flag命令行参数时停止,非flag命令行参数是指不满足命令行语法的参数,如命令行参数为cmd --flag=true abc则第一个非flag命令行参数为“abc”
flag解析后的参数使用
fmt.Println("ip has value ", *ip)
fmt.Println("flagvar has value ", flagvar)
命令行语法
-flag //只支持bool类型
-flag=x
-flag x //只支持非bool类型
注意第三种只支持非bool类型,原因就是如果文件的名字为false的话那么就会产生歧义。看例子:
package main
import (
"flag"
"fmt"
)
var name string
var right bool
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.")
flag.BoolVar(&right, "check", false, "The right ob")
}
func main() {
flag.Parse()
fmt.Println(name)
fmt.Println(right)
}
这里采用的还是老师的例子,只多加了个bool类型。
我们编译之后生成exe
demo4.exe -check false
demo4.exe -check
对于这两种都会采用默认的bool类型true。
demo4.exe -check=false
只有这样的时候,才会采用false
-name 无论有无=都会显示传入的字符串。
eg2:
package main
import (
"flag"
"fmt"
)
var name string
var name1 string
func init() {
flag.StringVar(&name, "name", "Nobody", "请设置名字")
name1 = *flag.String("name1", "Nobody", "请设置名字")
}
func main() {
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
fmt.Printf("Hello, %s!\n", name1)
}
函数flag.StringVar接受4个参数:
- 第1个参数,是用于存储该命令参数的值的地址,就是示例中声明的变量name的地址,由表达式&name表示
- 第2个参数,是为了指定该命令参数的名称,这里是name
- 第3个参数,是为了指定在未追加该命令参数时的默认值,这里是Nobody
- 第4个参数,是该命令参数的简短说明,这在打印命令说明时会用到
还有一个函数flag.String和flag.StringVar类似。就是没有第一个参数了,而是直接返回一个已经分配好的用于存储命令参数值的地址。
函数调用顺序
函数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。
思考题
1. 默认情况下,我们可以让命令源码文件接受哪些类型的参数值?
这个从flag包的代码包里面就可以看到,这里贴一下班里一个同学的答案
int(int|int64|uint|uint64),
float(float|float64)
string,
bool,
duration(时间),
var(自定义)
2. 我们可以把自定义的数据类型作为参数值的类型吗?如果可以,怎样做?
当然是可以的:
// Var defines a flag with the specified name and usage string. The type and
// value of the flag are represented by the first argument, of type Value, which
// typically holds a user-defined implementation of Value. For instance, the
// caller could create a flag that turns a comma-separated string into a slice
// of strings by giving the slice the methods of Value; in particular, Set would
// decompose the comma-separated string into the slice.
func Var(value Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
type pepoInfo struct{
name string
age int
homeAdress string
}
func (p * pepoInfo) Set(val string) error{
peopleList := strings.Split(val, ",")
p.name = peopleList[0]
p.age,_ = strconv.Atoi(peopleList[1])
p.homeAdress = peopleList[2]
return nil
}
func (s *pepoInfo) String() string {
infoList := strings.Split("wenxuwan,20,山东", ",")
s.name = infoList[0]
s.age,_ = strconv.Atoi(infoList[1])
s.homeAdress = infoList[2]
return "It's none of my business"
}
func main(){
var people pepoInfo
flag.Var(&people, "info", "hao ma fan")
flag.Parse()
//打印结果slice接收到的值
fmt.Println(people)
}
demo4.exe --info wenxuwan,10,shouguang