1.命令源码文件
命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。
如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。 就像下面这段代码:
1 | package main |
这其实就是就简单的可以运行go的一个文件了,刚开始搭建的时候我也是使用这么一段测试代码,随便创建了一个文件之后,然后敲上这么一段代码之后发现控制台可以打印出来“Hello World!”,瞬间感觉就学会了这门语言了,可是事情并没有那么简单,有多难?很难很难。
但是这里让我挺困惑的地方就是,这func main()
,没有接收参数入口啊???难道我就只能进行打印了?这不科学,无论在C/C++,还是Java中,main方法都不是这么写的,那么问题就来了,如果进行接收参数呢?
1.1 命令源码文件进行传参
于是我们这一部分的主角就要登场了,没错,他就是:flag
flag
是一个包,是Go 语言标准库中有一个代码包专门用于接收和解析命令参数。
对于flag包,详细可以查看官方文档:Package flag
首先要使用一个包,必须先要导入这个包,当然是用我们的import 方法了,而且fmt这个包我们是必须要import的,这样一来我们需要import两个包了:
1 | import ( |
那我们我们需要传入一串字符串,比如我们想要拿某样东西,I want to take “xxx”
,我们可以定义一个Sring类型的变量name:var name string
来表示我们需要拿到的东西,这样的话,我想传入一个字符串,那么我该如何才能达到目的呢?通过查阅文档,我们发现flag提供了一个绝妙的方法,就是flag.StringVar
他是这么定义的:
1 | func StringVar(p *string, name string, value string, usage string) |
StringVar定义了一个有指定名字,默认值,和用法说明的string标签。 参数p指向一个存储标签解析值的string变量。
这四个参数的详解如下:
- 参数1:是用于存储该命令参数值的地址
- 参数2:是为了指定该命令参数的名称
- 参数3:是为了指定在未追加该命令参数时的默认值
- 函数参数4:即是该命令参数的简短说明了
那我们就可以这么写了:
1 | flag.StringVar(&name, "name", "momey", "The greeting object.") |
大概意思就是去取name这个变量,传入我们需要的参数”momey”,就可以达到我们传参的目的了。
我们发现还有一个类似flag.StringVar
的函数:flag.String
,这两个函数的区别是,后者会直接返回一个已经分配好的用于存储命令参数值的地址。这里暂不展开讲解,记住我们的任务,传参传参传参!
那么经过使用上面的flag.StringVal()
,我们就可以达到目的了吗?
当然还没有,我们这里还需要一个解析函数,函数flag.Parse
用于真正解析命令参数,并把它们的值赋给相应的变量。
对该函数flag.Parse()
的调用必须在所有命令参数存储载体的声明(对变量name的声明)和设置(对flag.StringVar函数的调用)之后,并且在读取任何命令参数值之前进行。
所以我们最好就将flag.Parse()
放在main函数的函数体的第一行。
则我们需要完成的代码框架如下:
1 | package main |
输出如下:
总的来说我们的小目标算是完成了!
1.2 如何查看参数说明
我们使用Cmder工具进行命令行处理上面编写的demo,看能否得到详细的命令源码文件参数说明
我们运行demo:
1 | go run demo2.go -name="some money" |
发现能够打印输出,完成了传参功能,这并没有什么问题。
但是我们想看更加详细的内容呢?可以使用如下命令:
1 | go run demo2.go --help |
发现这次有所不一样了,我们发现了挺多细节,比如:"-name string"
,这个name是string类型的,其默认是:”money”
这里可能会发现:Usage of C:\Users\dongxiem\AppData\Local\Temp\go-build807196986\b001\exe\demo2.exe:
这么长一串?其实这个只是go run
命令构建上述命令源码文件时临时生成的可执行文件的完整路径。
1.3 自定义命令源码文件的参数使用说明
这个里面的内容还有些复杂,上面我们得到这么一串Usage of C:\Users\dongxiem\AppData\Local\Temp\go-build807196986\b001\exe\demo2.exe:
的打印输出,看起来还挺复杂的,那么如果我不想要这么复杂可以吗?我只想简简单单,让我能够了解内容就行了。那当然是可以的,而且办法还不只有一种。
第一种方法:重新赋值变量flag.Usage
flag.Usage
的类型是func()
,即一种无参数声明且无结果声明的函数类型。
注意,对flag.Usage的赋值必须在调用flag.Parse函数之前!
我们修改Demo代码为:
1 | package main |
运行之后查看输出如下:
第二种方式:调用flag.CommandLine变量
其实我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine
变量的对应方法。所以我们也可以直接对flag.CommandLine
进行操作,这也不是不可以的!
flag.CommandLine
相当于默认情况下的命令参数容器。所以,通过对flag.CommandLine
重新赋值,我们可以更深层次地定制当前命令源码文件的参数使用说明。
于是我们继续修改测试代码:
1 | package main |
运行以上Demo,打印输出如下:
发现和第一种方法打印输出的东西是一样的,看起来起到了同样的效果了!
但是,如果我们使用flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)
替换上面Demo中的flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
,我们会发现,似乎有了一些不一样的东西了,这是怎么回事?
首先,flag.PanicOnError
和flag.ExitOnError
都是预定义在flag包中的常量。
flag.ExitOnError
的含义是,告诉命令参数容器,当命令后跟–help或者参数设置的不正确的时候,在打印命令参数使用说明后以状态码2结束当前程序。状态码2代表用户错误地使用了命令flag.PanicOnError
与之的区别是在最后抛出“运行时恐慌(panic)”。
上述两种情况都会在我们调用flag.Parse
函数时被触发。顺便提一句,“运行时恐慌”是 Go 程序错误处理方面的概念。
第三种 创建一个私有的命令参数容器
我们可以不用全局的flag.CommandLine
变量,转而自己创建一个私有的命令参数容器。我们在函数外再添加一个变量声明:var cmdLine = flag.NewFlagSet("Dongxiem", flag.ExitOnError)
然后,我们把对flag.StringVar
的调用替换为对cmdLine.StringVar
调用,再把flag.Parse()
替换为cmdLine.Parse(os.Args[1:])
os.Args[1:]
指的就是我们给定的那些命令参数。这样做就完全脱离了flag.CommandLine
。
1 | package main |
然后我们发现运行之后的打印如下: