命令源码文件如何接收参数?
Go 语言标准库中有一个代码包专门用于接收和解析命令参数,即flag包。
使用案例
- 根据运行程序时给定的参数问候某人
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "the greeting object")
//flag.StringVar()参数解析:
//第 1 个参数是用于存储该命令参数的值的地址
//第 2 个参数是为了指定该命令参数的名称
//第 3 个参数是为了指定在未追加该命令时的默认参数
//第 4 个参数是该命令参数的简短说明
//对比flag.String()
//这两个函数的区别是,后者会直接返回一个已经分配好的用于存储命令参数值的地址
//这里使用为var name = flag.String("name", "everyone", "the greeting object")
}
func main() {
flag.Parse()
//函数flag.Parse用于真正解析命令参数,并把它们的值赋给相应的变量。
//对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量name的声明)和设置(这
//里是指flag.StringVar函数的调用)之后,并且在读取任何命令参数值之前进行。
//正因为如此,我们最好把flag.Parse()放在main函数的函数体的第一行
fmt.Printf("Hello, %s!\n", name)
}
执行:
go run learn_flag.go -name="zcm"
输出:
Hello, zcm!
怎样自定义命令源码文件的参数使用说明
方案1:对flag.Usage变量重新赋值
- 对变量flag.Usage重新赋值。flag.Usage的类型是func(),即一种无参数声明且无结果声明的函数类型
- flag.Usage变量在声明时就已经被赋值
- 对flag.Usage的赋值必须在调用flag.Parse函数之前
package main
import (
"flag"
"fmt"
"os"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "the greeting object")
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
//当输入 go run main.go --help时,被视为错误命令输入
flag.PrintDefaults()
}
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
输入:
go run learn_flag.go --help
输出:
Usage of question:
-name string
the greeting object (default "everyone")
exit status 2
方案2:更深一层,利用flag.CommandLine命令参数容器
- 我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
- flag.CommandLine相当于默认情况下的命令参数容器。通过对flag.CommandLine重新赋值,可以更深层次地定制当前命令源码文件的参数使用说明
package main
import (
"flag"
"fmt"
"os"
)
var name string
func init() {
flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
//flag.ExitOnError的含义是,告诉命令参数容器,当命令后跟--help或者参数设置的不正
//确的时候,在打印命令参数使用说明后以状态码2结束当前程序。
// 状态码2代表用户错误地使用了命令,而flag.PanicOnError与之的区别是在最后抛出“运
//行时恐慌(panic)”。上述两种情况都会在我们调用flag.Parse函数时被触发
flag.CommandLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
//以上代码必须放在flag.StringVar等参数设置之前
flag.StringVar(&name, "name", "everyone", "the greeting object")
}
func main() {
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
输出与方案1一样
若把flag.ExitOnError 改为flag.PanicOnError
在输出解释后,引发panic
创建私有命令参数容器
package main
import (
"flag"
"fmt"
"os"
)
var name string
var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)
func init() {
cmdLine.StringVar(&name, "name", "everyone", "the greeting object")
}
func main() {
cmdLine.Parse(os.Args[1:])
fmt.Printf("Hello, %s!\n", name)
}
- 不用全局的flag.CommandLine变量,转而自己创建一个私有的命令
参数容器 - os.Args[1:]指的就是我们给定的那些命令参数。这样做就完全脱离了
flag.CommandLine。*flag.FlagSet类型的变量cmdLine拥有很多有意思的方法
其余问题
-
可以让命令源码文件接受哪些类型的参数值?
命令源码文件支持的参数:
int(int|int64|uint|uint64),
float(float|float64)
string,
bool,
duration(时间),
var(自定义) -
可以把自定义的数据类型作为参数值的类型吗?如果可以,怎样做?
参数值的类型可以是自定义的数据类型,重点是实现flag包里的Value接口, 然后使用flag.Var()实现。 -
试着把参数增加到两个,结果是受StringVar声明顺序影响还是受参数传递顺序影响
func init() {
flag.StringVar(&name, "name1", "ladies", "The greeting object 1")
flag.StringVar(&name, "name2", "gentlemen", "The greeting objec t 2")
}
- go run test.go
Hello gentlemen !
name2的默认值覆盖了name1的默认值 - go run test.go -name1=Robert
Hello Robert!
只指定了name1,没有指定name2,输出了name1的指定值,name2的
默认值没有生效 - go run test.go -name2=Jose
Hello Jose!
没毛病 - go run test.go -name1=Robert -name2=Jose
Hello Jose!
没毛病 - go run test.go -name2=Jose -name1=Robert
Hello Robert!
输出的值是以参数的先后顺序为准的,而不是以flag.StringVar函数的声明顺序为准的