【Go】笔记二 | 命令源码文件与命令行参数
首发地址:命令源码文件与命令行参数
核心知识
- 自定义命令参数
- 创建私有的命令参数容器
首知
- 环境变量 GOPATH 指向的是一个或多个工作区,而每个工作区中都会有以代码包为基本组织形式的源码文件。
- 源码文件又分为三种,即:命令源码文件、库源码文件和测试源码文件,它们都有着不同的用途和编写规则。
命令源码文件的用途是什么,怎样编写它?
典型回答
- 命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。
- 我们可以通过构建或安装生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。
通过构建或安装命令源码文件生成的可执行文件就可以被视为“命令”,既然是命令,那么就应该具备接收参数的能力。
例如,要实现
根据运行程序时给定的参数问候某人
代码
package main
import (
"flag" // [1]
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]
}
func main() {
flag.Parse() // [3]
fmt.Printf("Hello, %s!\n", name)
}
解析
[1]导入flag包,这是Go 语言标准库中专门用于接收和解析命令参数的包。
[2]设置命令参数
接受 4 个参数。
- 第 1 个参数是用于存储该命令参数的值的地址,具体到这里就是在前面声明的变量name的地址了,由表达式&name表示。
- 第 2 个参数是为了指定该命令参数的名称,这里是name.
- 第 3 个参数是为了指定在未追加该命令参数时的默认值,这里是everyone
- 第 4 个函数参数,即是该命令参数的简短说明了,这在打印命令说明时会用到
还有一个与flag.StringVar函数类似的函数,叫flag.String。
这两个函数的区别是,后者会直接返回一个已经分配好的用于存储命令参数值的地址。
var name = flag.String("name", "everyone", "The greeting object.")
这里的name保持的就是地址
[3]用于真正解析命令参数,并把它们的值赋给相应的变量。
因此最好把flag.Parse()放在main函数的函数体的第一行。
查看该命令源码文件的参数说明,–help:
像上面这一串/tmp/go-build343904153/b001/exe/demo1
代码
这是命令构建上述命令源码文件时临时生成的可执行文件的完整路径。
如果我们先构建这个命令源码文件再运行生成的可执行文件,就会这样:
自定义命令源码文件的参数使用说明
这有很多种方式,最简单的一种方式就是对变量flag.Usag重新赋值。
flag.Usag重新赋值
flag.Usage的类型是func(),即一种无参数声明且无结果声明的函数类型。
注意,对flag.Usage的赋值必须在调用flag.Parse函数之前
代码
import (
"flag" // [1]
"fmt"
"os"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]
}
func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
flag.Parse() // [3]
fmt.Printf("Hello, %s!\n", name)
}
运行
flag.CommandLine重新赋值
现在再深入一层,我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
flag.CommandLine相当于默认情况下的命令参数容器。所以,通过对flag.CommandLine重新赋值,我们可以更深层次地定制当前命令源码文件的参数使用说明。
现在我们把main函数体中的那条对flag.Usage变量的赋值语句注销掉,然后在init函数体的开始处添加如下代码:
flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
flag.CommandLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
运行 go run demo.go –help后,其输出会与上一次的输出的一致。不过后面这种定制的方法更加灵活。比如,当我们把为flag.CommandLine赋值的那条语句改为
flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)
运行结果
会产生另一种输出效果。
这是由于我们在这里传给flag.NewFlagSet函数的第二个参数值是flag.PanicOnError。
flag.PanicOnError和flag.ExitOnError都是预定义在flag包中的常量。
flag.ExitOnError的含义是,告诉命令参数容器,当命令后跟–help或者参数设置的不正确的时候,在打印命令参数使用说明后以状态码2结束当前程序。
状态码2代表用户错误地使用了命令,而flag.PanicOnError与之的区别是在最后抛出“运行时恐慌(panic)”。
上述两种情况都会在我们调用flag.Parse函数时被触发。
运行时恐慌”是 Go 程序错误处理方面的概念
创建私有的命令参数容器
在函数外再添加一个变量声明:
var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)
完整代码
package main
import (
"flag" // [1]
"fmt"
"os"
)
var name string
var cmdLine = flag.NewFlagSet("", flag.ExitOnError)
func init() {
cmdLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
cmdLine.PrintDefaults()
}
cmdLine.StringVar(&name, "name", "everyone", "The greeting object.") // [2]
}
func main() {
cmdLine.Parse(os.Args[1:]) // [3]
fmt.Printf("Hello, %s!\n", name)
}
其中的os.Args[1:]指的就是我们给定的那些命令参数。这样做就完全脱离了flag.CommandLine。
这样更加灵活定制命令参数容器,并且完全不会影响到那个全局变量flag.CommandLine。
额外知识
默认情况下,我们可以让命令源码文件接受哪些类型的参数值?
int(int|int64|uint|uint64),
float(float|float64)
string,
bool,
duration(时间),
var(自定义)
我们可以把自定义的数据类型作为参数值的类型吗?如果可以,怎样做?
关键就是使用flag.var(),关键点在于需要实现flag包的Value接口。
小结
- 源码文件分为三种:命令,库,测试。
- 编写命令源码文件的命令参数关键包: flag。
- 可以通过重新赋值创建新的命令参数容器
上一篇: C++ inline函数与宏定义的区别
下一篇: 创建随机点样本