0%

golang 基础 - go的命令源码文件

1.命令源码文件

命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。

如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。 就像下面这段代码:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello, world!")
}

这其实就是就简单的可以运行go的一个文件了,刚开始搭建的时候我也是使用这么一段测试代码,随便创建了一个文件之后,然后敲上这么一段代码之后发现控制台可以打印出来“Hello World!”,瞬间感觉就学会了这门语言了,可是事情并没有那么简单,有多难?很难很难。

但是这里让我挺困惑的地方就是,func main(),没有接收参数入口啊???难道我就只能进行打印了?这不科学,无论在C/C++,还是Java中,main方法都不是这么写的,那么问题就来了,如果进行接收参数呢?

1.1 命令源码文件进行传参

于是我们这一部分的主角就要登场了,没错,他就是:flag

flag 是一个包,是Go 语言标准库中有一个代码包专门用于接收和解析命令参数。

对于flag包,详细可以查看官方文档:Package flag

首先要使用一个包,必须先要导入这个包,当然是用我们的import 方法了,而且fmt这个包我们是必须要import的,这样一来我们需要import两个包了:

1
2
3
4
import (
"flag"
"fmt"
)

那我们我们需要传入一串字符串,比如我们想要拿某样东西,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. 参数1:是用于存储该命令参数值的地址
  2. 参数2:是为了指定该命令参数的名称
  3. 参数3:是为了指定在未追加该命令参数时的默认值
  4. 函数参数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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"flag"
"fmt"
)

var name string

func init() {
flag.StringVar(&name, "name", "momey", "The greeting object.")
}

func main() {
flag.Parse()
fmt.Printf("I want to take %s! \n", name) // 有没有发现跟SQL传参有些类似
}

输出如下:

使用StringVar输出

总的来说我们的小目标算是完成了!


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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"flag"
"fmt"
"os"
)

var name string

func init() {
flag.StringVar(&name, "name", "momey", "The greeting object.")
}

func main() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s : \n", "Dongxiem")
flag.PrintDefaults()
}

flag.Parse()
fmt.Printf("I want to take %s! \n", name)
}

运行之后查看输出如下:

修改之后运行如上

第二种方式:调用flag.CommandLine变量

其实我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。所以我们也可以直接对flag.CommandLine进行操作,这也不是不可以的!

flag.CommandLine相当于默认情况下的命令参数容器。所以,通过对flag.CommandLine重新赋值,我们可以更深层次地定制当前命令源码文件的参数使用说明。

于是我们继续修改测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"flag"
"fmt"
"os"
)

var name string

func init() {
// 使用flag.CommandLine 修改go run demo.go --help 打印输出
flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
flag.CommandLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "Dongxiem")
flag.PrintDefaults()
}

flag.StringVar(&name, "name", "momey", "The greeting object.")
}

func main() {
flag.Parse()
fmt.Printf("I want to take %s! \n", name)
}

运行以上Demo,打印输出如下:

使用flag.CommandLine进行修改

发现和第一种方法打印输出的东西是一样的,看起来起到了同样的效果了!

但是,如果我们使用flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)替换上面Demo中的flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError),我们会发现,似乎有了一些不一样的东西了,这是怎么回事?

替换之后进行运行

首先,flag.PanicOnErrorflag.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"flag"
"fmt"
"os"
)

var name string

var cmdLine = flag.NewFlagSet("Dongxiem", flag.ExitOnError)

func init() {

cmdLine.StringVar(&name, "name", "some money", "The greeting object.")
}

func main() {
cmdLine.Parse(os.Args[1:])
fmt.Printf("I want to take %s! \n", name)
}

然后我们发现运行之后的打印如下:

还是一样起到作用