转载自:https://halfrost.com/go_command/
引言
Go 语言这两年在语言排行榜上的上升势头非常猛,Go 语言虽然是静态编译型语言,但是它却拥有脚本化的语法,支持多种编程范式(函数式和面向对象)。Go 语言最最吸引人的地方可能是其原生支持并发编程(语言层面原生支持和通过第三方库支持是有很大区别的)。Go 语言的对网络通信、并发和并行编程的支持度极高,从而可以更好地利用大量的分布式和多核的计算机。开发者可以通过 goroutine 这种轻量级线程的概念来实现这个目标,然后通过 channel 来实现各个 goroutine 之间的通信。他们实现了分段栈增长和 goroutine 在线程基础上多路复用技术的自动化。

2017年7月 TIOBE 语言排行榜 Go 首次进入前十。今天就让我们来探究探究 Go 的编译命令执行过程。
一. 理解 Go 的环境变量 1. GOROOT该环境变量的值为 Go 语言的当前安装目录。
2. GOPATH该环境变量的值为 Go 语言的工作区的 集合(意味着可以有很多个) 。工作区类似于工作目录。每个不同的目录之间用 : 分隔。
工作区是放置 Go 源码文件的目录。一般情况下,Go 源码文件都需要存放到工作区中。
工作区一般会包含3个子文件夹,自己手动新建以下三个目录:src 目录,pkg 目录,bin 目录。
Go
/home/halfrost/gorepo ├── bin ├── pkg └── src这里需要额外说的一点:关于 IDE 新建 Go 项目。IDE 在新建完 Go 的项目以后,会自动的执行 go get 命令去把相应的基础包拉过来, 在这个过程中会新建 bin、pkg、src 三个目录。不用 IDE 的同学,需要自己手动创建这三个目录。

上图是 Atom 的 go-plus 插件在一个新的项目打开的时候,自动 go get 的一些基础包。
bin 目录里面存放的都是通过 go install 命令安装后,由 Go 命令源码文件生成的可执行文件( 在 Mac 平台下是 Unix executable 文件,在 windows 平台下是 exe 文件)。
注意 :有两种情况下,bin 目录会变得没有意义。1. 当设置了有效的 GOBIN 环境变量以后,bin 目录就变得没有意义。
2. 如果 GOPATH 里面包含多个工作区路径的时候,必须设置 GOBIN 环境变量,否则就无法安装 Go 程序的可执行文件。
pkg 目录是用来存放通过 go install 命令安装后的代码包的归档文件(.a 文件)。归档文件的名字就是代码包的名字。所有归档文件都会被存放到该目录下的平台相关目录中,即在 $GOPATH\/pkg\/$GOOS_$GOARCH 中,同样以代码包为组织形式。
这里有两个隐藏的环境变量,GOOS 和 GOARCH。这两个环境变量是不用我们设置的,系统就默认的。GOOS 是 Go 所在的操作系统类型,GOARCH 是 Go 所在的计算架构。平台相关目录是以 $GOOS_$GOARCH 命名的,Mac 平台上这个目录名就是 darwin_amd64。
src 目录是以代码包的形式组织并保存 Go 源码文件的。每个代码包都和 src 目录下的文件夹一一对应。每个子目录都是一个代码包。
这里有一个 特例 ,命令源码文件并不一定必须放在 src 文件夹中的。
这里需要纠正一个错误的观点:“所有的 Go 的代码都要放在 GOPATH 目录下”(这个观点是错误的)
说到这里需要谈到 Go 的源码文件分类:

如上图,分为三类:
(1)命令源码文件:
声明自己属于 main 代码包、包含无参数声明和结果声明的 main 函数。
命令源码文件被安装以后,GOPATH 如果只有一个工作区,那么相应的可执行文件会被存放当前工作区的 bin 文件夹下;如果有多个工作区,就会安装到 GOBIN 指向的目录下。
命令源码文件是 Go 程序的入口。
同一个代码包中最好也不要放多个命令源码文件。多个命令源码文件虽然可以分开单独 go run 运行起来,但是无法通过 go build 和 go install。
vim
YDZ ~/LeetCode_Go/helloworld/src/me $ ls helloworld.go helloworldd.go先说明一下,在上述文件夹中放了两个命令源码文件,同时都声明自己属于 main 代码包。helloworld.go 文件输出 hello world,helloworldd.go 文件输出 worldd hello。接下来执行 go build 和 go install ,看看会发生什么。
vim
YDZ ~/LeetCode_Go/helloworld/src/me $ go build # _/Users/YDZ/LeetCode_Go/helloworld/src/me ./helloworldd.go:7: main redeclared in this block previous declaration at ./helloworld.go:50 YDZ ~/LeetCode_Go/helloworld/src/me $ go install # _/Users/YDZ/LeetCode_Go/helloworld/src/me ./helloworldd.go:7: main redeclared in this block previous declaration at ./helloworld.go:50这也就证明了多个命令源码文件虽然可以分开单独 go run 运行起来,但是无法通过 go build 和 go install。
同理,如果命令源码文件和库源码文件也会出现这样的问题,库源码文件不能通过 go build 和 go install 这种常规的方法编译和安装。具体例子和上述类似,这里就不再贴代码了。
所以命令源码文件应该是被单独放在一个代码包中。
(2)库源码文件
库源码文件就是不具备命令源码文件上述两个特征的源码文件。存在于某个代码包中的普通的源码文件。
库源码文件被安装后,相应的归档文件(.a 文件)会被存放到当前工作区的 pkg 的平台相关目录下。
(3)测试源码文件
名称以 _test.go 为后缀的代码文件,并且必须包含 Test 或者 Benchmark 名称前缀的函数。
Go
func TestXXX( t *testing.T) { }名称以 Test 为名称前缀的函数,只能接受 *testing.T 的参数,这种测试函数是功能测试函数。
Go
func BenchmarkXXX( b *testing.B) { }名称以 Benchmark 为名称前缀的函数,只能接受 *testing.B 的参数,这种测试函数是性能测试函数。
现在答案就很明显了:
命令源码文件是可以单独运行的。可以使用 go run 命令直接运行,也可以通过 go build 或 go install 命令得到相应的可执行文件。所以命令源码文件是可以在机器的任何目录下运行的。
举个例子:
比如平时我们在 LeetCode 上刷算法题,这时候写的就是一个程序,这就是命令源码文件,可以在电脑的任意一个文件夹新建一个 go 文件就可以开始刷题了,写完就可以运行,对比执行结果,答案对了就可以提交代码。
但是公司项目里面的代码就不能这样了,只能存放在 GOPATH 目录下。因为公司项目不可能只有命令源码文件的,肯定是包含库源码文件,甚至包含测试源码文件的。
3.GOBIN该环境变量的值为 Go 程序的可执行文件的目录。
4.PATH为了方便使用 Go 语言命令和 Go 程序的可执行文件,需要添加其值。追加的操作还是用 : 分隔。
Go
export PATH=$PATH:$GOBIN以上就是关于 Go 的4个重要环境变量的理解。还有一些其他的环境变量,用 go env 命令就可以查看。
vim
YDZ ~ $ go env GOARCH="amd64" GOBIN="/Users/YDZ/Ele_Project/clairstormeye/bin" GOEXE="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOOS="darwin" GOPATH="/Users/YDZ/Ele_Project/clairstormeye" GORACE="" GOROOT="/usr/local/Cellar/go/1.8.3/libexec" GOTOOLDIR="/usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64" GCCGO="gccgo" CC="clang" GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/66/dcf61ty92rgd_xftrsxgx5yr0000gn/T/go-build977187889=/tmp/go-build -gno-record-gcc-switches -fno-common" CXX="clang++" CGO_ENABLED="1" PKG_CONFIG="pkg-config" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2"
在探索 Go 的编译命令之前,需要说明的一点是:
Go 程序是通过 package 来组织的。
package(假设我们的例子中是 package main)这一行告诉我们当前文件属于哪个包,而包名 main 则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了 main 包之外,其它的包最后都会生成 *.a 文件(也就是包文件)并放置在 $GOPATH/pkg/$GOOS_$GOARCH中(以 Mac 为例就是 $GOPATH/pkg/darwin_amd64 )。
Go 使用 package(和 python 的模块类似)来组织代码。main.main() 函数(这个函数位于主包)是每一个独立的可运行程序的入口点。
每一个可独立运行的 Go 程序,必定包含一个 package main,在这个 main 包中必定包含一个入口函数 main,而这个函数既没有参数,也没有返回值。
二. 初探 Go 的编译过程目前 Go 最新版1.8.3里面基本命令只有以下的16个。
Go
build compile packages and dependencies clean remove object files doc show documentation for package or symbol env print Go environment information bug start a bug report fix run go tool fix on packages fmt run gofmt on package sources generate generate Go files by processing source get download and install packages and dependencies install compile and install packages and dependencies list list packages run compile and run Go program test test packages tool run specified go tool version print Go version vet run go tool vet on packages其中和编译相关的有 build、get、install、run 这4个。接下来就依次看看这四个的作用。
在详细分析这4个命令之前,先罗列一下通用的命令标记,以下这些命令都可适用的:

1. go run
专门用来运行命令源码文件的命令, 注意,这个命令不是用来运行所有 Go 的源码文件的!
go run 命令只能接受一个命令源码文件以及若干个库源码文件(必须同属于 main 包)作为文件参数,且 不能接受测试源码文件 。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件,那么 go run 命令就只会打印错误提示信息并退出,而不会继续执行。
这个命令具体干了些什么事情呢?来分析分析:
vim
YDZ ~/LeetCode_Go/helloworld/src/me $ go run -n helloworld.go # # command-line-arguments # mkdir -p $WORK/command-line-arguments/_obj/ mkdir -p $WORK/command-line-arguments/_obj/exe/ cd /Users/YDZ/LeetCode_Go/helloworld/src/me /usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/compile -o $WORK/command-line-arguments.a -trimpath $WORK -p main -complete -buildid 2841ae50ca62b7a3671974e64d76e198a2155ee7 -D _/Users/YDZ/LeetCode_Go/helloworld/src/me -I $WORK -pack ./helloworld.go cd . /usr/local/Cellar/go/1.8.3/libexec/pkg/tool/darwin_amd64/link -o $WORK/command-line-arguments/_obj/exe/helloworld -L $WORK -w -extld=clang -buildmode=exe -buildid=2841ae50ca62b7a3671974e64d76e198a2155