Go语言核心36讲-02工作区和GOPATH

02 | 命令源码文件

我们已经知道,环境变量 GOPATH 指向的是一个或多个工作区,而每个工作区中都会有以代码包为基本组织形式的源码文件。

这里的源码文件又分为三种,即:命令源码文件、库源码文件和测试源码文件,它们都有着不同的用途和编写规则。

1. 什么是命令源码文件,用途是什么?如何编写它

1.1 命令源码文件:

==命令源码文件是程序的运行入口,是每个可独立运行的程序必须拥有的。==

我们可以通过构建或安装生成与其对应的可执行文件,后者一般会与该命令源码文件的直接父目录同名。

==命令源码文件可以很方便地用 go run xxx.go 命令启动运行==

如果一个源码文件声明属于 main 包(不唯一,在同一级目录下有多个文件声明包名称都是一样的),并且包含一个无参数声明且无返回结果声明的 main 函数(这个 main 函数式唯一的),那么就是命令源码文件了。如下:

package main

import "fmt"

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

注意: 你可以把这段代码名为任意文件名如:demo1.go,然后敲命令:

go run demo1.go

终端可以看见如下输出:

Hello world!

但是,当你拆分代码包需要模块化编程时:

==命令源码文件永远只会有一个,如果有与命令源码文件同包的源码文件,那它们就必须声明成 main 包。看下图所示:==

如图所示 demo.go 和 main.go 文件都属于同一级目录下(最外层目录),当main.go 的包名声明成为 package main (也只能声明成 main 包,不然go run main.go 命令跑不起来,它会报错 “ cannot run non-main package”)时,demo.go 也应该声明为 package main 包,不然也会编译报错的!

==所以同级目录下,源码文件的 包声明必须一致。==


2. 知识精讲

无论是 Linux 还是 Windows,如果你用过命令行(command line)的话,肯定就会知道几乎所有命令(command)都是可以接收参数(argument)的。通过构建或安装命令源码文件生成的可执行文件就可以被视为“命令”,既然是命令,那么就应该具备接收参数的能力。

2.1 命令源码文件怎样接收参数

如下代码:

package main

import (
    // 需在此处添加代码。[1]
    "fmt"
)

var name string

func init() {
    // 需在此处添加代码。[2]
}

func main() {
    // 需在此处添加代码。[3]
    fmt.Printf("Hello, %s!\n", name)
}

更新上面代码需求添加处,让程序运行时实现: “给定参数问候某人” 的功能。

==flag 代码包:Go 语言标准库中专门用于接收和解析命令参数。==

我们要用到 :

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

该方法接受四个参数分别表示:

  1. 用于存储该命令参数值的地址,这里就是变量name的地址了,由表达式 &name 表示。
  2. 指定该命令参数的名称,这里就是 name.
  3. 指定在未追加该命令参数时的默认值,这里就是 everyone.
  4. 该命令参数的简短说明,在打印命令说明时会用到。

你可以下来查下 ,上面的StirngVar 与下面的区别

func String(name string, value string, usage string) *string


var name = flag.String("name", "everyone", "The greeting object.")

再来看最后一空:

  flag.Pare()

用于真正解析命令参数,并把其值赋给相应的变量。

对该函数的调用必须在所有命令参数存储载体的声明(这里是对变量的声明)和设置(这里是在处对函数的调用)之后,并且在读取任何命令参数值之前进行。

即完成代码如下:

package main

import (
    "flag"
    "fmt"
)

var name string

func init() { // 初始化给 flag包 注册
       flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
    flag.Pare() // 解析命令参数 取变量值
    fmt.Printf("Hello, %s!\n", name)
}

2.2 如何运行命令源码文件时传入参数?如何查看参数说明?

在你的终端运行输入如下命令:

go run demo2.go -name="Gopherzhang"

此处的 -name 对应了 flag.StringVar()的第二个参数。

终端标注输出为:

Hello Gopherzhang!

另外,如果想查看该命令源码文件的参数说明,可以这样做:

$ go run demo2.go --help

然后你会看见:

Usage of /var/folders/rb/24416mc957df0tf5c5yt8nbc0000gn/T/go-build793417593/b001/exe/demo2:
  -name string
        The greeting object. (default "everyone")
exit status 2  // 状态码2代表用户错误地使用了命令

极客时间版权所有: https://time.geekbang.org/column/article/13159

其中

Usage of /var/folders/rb/24416mc957df0tf5c5yt8nbc0000gn/T/go-build793417593/b001/exe/demo2:

这个路径是,命令构建上述命令源码文件时临时生成的可执行文件的完整路径。

如果我们进行构建在运行命令:

$ go build demo2.go
$ ./demo2 --help

输出如下:

    Usage of ./demo2:
  -name string
        The greeting object. (default "everyone")

2.3 怎样自定义命令源码文件的参数使用说明

对 变量 flag.Usage 重新赋值。

==注意: flag.Usage的赋值必须再调用 flag.Parse 函数之前==

代码如下:

package main

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

var name string

// 方式3。
// var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)

func init() {
    // 方式2。
    flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
    flag.CommandLine.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
        flag.PrintDefaults()
    }
    // 方式3。
    // cmdLine.StringVar(&name, "name", "everyone", "The greeting object.")
    flag.StringVar(&name, "name", "everyone", "The greeting object.")
}

func main() {
    // 方式1。
    //flag.Usage = func() {
    //  fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
    //  flag.PrintDefaults()
    //}
    // 方式3。
    // cmdLine.Parse(os.Args[1:])
    flag.Parse()
    fmt.Printf("Hello, %s!\n", name)
}

小结:

  1. 源码文件有三种: 命令, 库, 测试源码文件。
  2. 自定义命令源码文件的参数关键包: flag
  3. 命令源码文件支持的参数:
    • int
    • float
    • string
    • bool
    • duration
    • var(自定义)