目录
一.引言
上文我们安装了 Go 语言的安装包,本文我们主要了解什么是命令源码文件,并执行自己的第一个 go 代码。
二.命令源码文件
1.什么是 `命令源码文件` ?
命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。我们可以通过构建或安装,生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。
2.`命令源码文件` 示例
- GoLand 执行
如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。 就像下面这段代码:
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
在 GoLand 根目录下创建 TestMain.go 文件添加上述代码并运行即可获得下述结果:
- Terminal 执行
直接在 Terminal 中例如 Iterm 中输入:
go run TestMain.go
同样可以获取下述结果:
- Tips
当需要模块化编程时,我们往往会将代码拆分到多个文件,甚至拆分到不同的代码包中。但无论怎样,对于一个独立的程序来说,命令源码文件永远只会也只能有一个。如果有与命令源码文件同包的源码文件,那么它们也应该声明属于 main 包。
三.程序扩展
1.代码框架
执行 Hello World 只是开始,下面我们尝试执行更复杂的任务,我们接收参数 name 并向 name 打招呼,下面我们基于代码框架进行填空操作。
package main
import (
// 需在此处添加代码。[1]
"fmt"
)
var name string
func init() {
// 需在此处添加代码。[2]
}
func main() {
// 需在此处添加代码。[3]
fmt.Printf("Hello, %s!\n", name)
}
2.代码分析
首先,Go 语言标准库中有一个代码包专门用于接收和解析命令参数,这个代码包的名字叫 flag
如果想要在代码中使用某个包中的程序实体,那么应该先导入这个包。因此:
我们需要在 [1] 处添加代码 "flag"
注意,这里应该在代码包导入路径的前后加上英文半角的引号。如此一来,上述代码导入了 flag 和 fmt 这两个包。其次,人名肯定是由字符串代表的。所以我们要在 [2] 处添加调用 flag 包的 StringVar 函数的代码。就像这样:
flag.StringVar(&name, "name", "everyone", "The greeting object.")
顺便说一下,还有一个与 flag.StringVar 函数类似的函数,叫 flag.String。这两个函数的区别是,后者会直接返回一个已经分配好的用于存储命令参数值的地址。如果使用它的话,我们就需要做如下改动,不过这里我们使用前者
再说最后一个填空。我们需要在 [3] 处添加代码 flag.Parse()。函数 flag.Parse 用于真正解析命令参数,并把它们的值赋给相应的变量。对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量 name 的声明)和设置(这里是在 [2] 处对 flag.StringVar 函数的调用)之后,并且在读取任何命令参数值之前进行。正因为如此,我们最好把 flag.Parse() 放在 main 函数的函数体的第一行。 有过 Java、Scala 或者 Python 开发经验的同学,这里就像他们的 Sys.args 一样,用于接收我们外部的传参。
3.代码运行
填空后的代码如下:
package main
import (
"flag"
"fmt"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
我们的程序目的是接收 $name 参数,并向 $name 打招呼,在命令行执行下述命令:
go run TestFlag.go -name="BITDDD"
四.源码文件参数说明
1.使用 -help
go run TestFlag.go -help
传入 -help 参数获取相关参数说明:
第一行 Usage 开头的是 `go run` 命令构建上述命令源码文件时临时生成的可执行文件的完整路径。 如果我们先构建这个命令源码文件再运行生成的可执行文件,像这样:
go build TestFlag.go
./TestFlag -help
我们的 Usage 地址就切换了,因为已经通过 build 在本地生成了:
2.自定义源码参数文件说明
这有很多种方式,最简单的一种方式就是对变量 flag.Usage 重新赋值。flag.Usage 的类型是func(),即一种无参数声明且无结果声明的函数类型。
flag.Usage 变量在声明时就已经被赋值了,所以我们才能够在运行命令 go run xx.go --help 时看到正确的结果。注意,对 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")
flag.PrintDefaults()
}
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
再次执行 -help 可以看到 Usage 的名称也会对应的改变:
3.参数进阶
现在再深入一层,我们在调用 flag 包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用 flag.CommandLine 变量的对应方法。flag.CommandLine 相当于默认情况下的命令参数容器。所以,通过对 flag.CommandLine 重新赋值,我们可以更深层次地定制当前命令源码文件的参数使用说明。再进一步,我们索性不用全局的 flag.CommandLine 变量,转而自己创建一个私有的命令参数容器。
package main
import (
"flag"
"fmt"
"os"
)
var name string
var source string
var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)
func init() {
cmdLine.StringVar(&name, "name", "everyone", "The greeting object.")
cmdLine.StringVar(&source, "source", "none", "The greeting source.")
}
func main() {
cmdLine.Parse(os.Args[1:])
fmt.Printf("Hello, %s from %s!\n", name, source)
}
其中的 os.Args[1:] 指的就是我们给定的那些命令参数。这样做就完全脱离了 flag.CommandLine。*flag.FlagSet 类型的变量 cmdLine 拥有很多有意思的方法,你可以去探索一下。这样做的好处依然是更灵活地定制命令参数容器。但更重要的是,你的定制完全不会影响到那个全局变量flag.CommandLine。
五.总结
现在我们已经走出了 Go 语言编程的第一步,我们可以使用 Go 编写命令,可以通过 Go Build 构建可执行的 .go 文件直接运行,并且可以通过命令行传参。继续加油 💪🏻