# go 命令

Go is a tool for managing Go source code.

Usage:

	go <command> [arguments]

The commands are:

	bug         start a bug report
	build       compile packages and dependencies
	clean       remove object files and cached files
	doc         show documentation for package or symbol
	env         print Go environment information
	fix         update packages to use new APIs
	fmt         gofmt (reformat) package sources
	generate    generate Go files by processing source
	get         add dependencies to current module and install them
	install     compile and install packages and dependencies
	list        list packages or modules
	mod         module maintenance
	run         compile and run Go program
	test        test packages
	tool        run specified go tool
	version     print Go version
	vet         report likely mistakes in packages

Use "go help <command>" for more information about a command.

# 一、通用选项

可选项 作用
-a 用于强制重新编译所有涉及的 Go 语言代码包(包括 Go 语言标准库中的代码包),即使它们已经是最新的了。该标记可以让我们有机会通过改动底层的代码包做一些实验。
-n 使命令仅打印其执行过程中用到的所有命令,而不去真正执行它们。如果只想查看或者验证命令的执行过程,而不想改变任何东西,使用它正好合适。
-race 用于检测并报告指定 Go 语言程序中存在的数据竞争问题。当用 Go 语言编写并发程序的时候,这是很重要的检测手段之一。
-v 用于打印命令执行过程中涉及的代码包。这一定包括我们指定的目标代码包,并且有时还会包括该代码包直接或间接依赖的那些代码包。这会让你知道哪些代码包被执行过了。
-work 用于打印命令执行时生成和使用的临时工作目录的名字,且命令执行完成后不删除它。这个目录下的文件可能会对你有用,也可以从侧面了解命令的执行过程。如果不添加此标记,那么临时工作目录会在命令执行完毕前删除。
-x 使命令打印其执行过程中用到的所有命令,并同时执行它们。

# 二、go run

go run 专门用来运行命令源码文件。

注意,这个命令不是用来运行所有 Go 的源码文件的!

go run 命令只能接受一个命令源码文件以及若干个库源码文件(必须同属于 main 包)作为文件参数,且不能接受测试源码文件。它在执行时会检查源码文件的类型。如果参数中有多个或者没有命令源码文件,那么 go run 命令就只会打印错误提示信息并退出,而不会继续执行。

下面来看看 go run 做了什么:

1. 准备如下代码 main.go

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World!")
	fmt.Println("Test go run")
}

2. go run 配合 -n

执行:

go run -n main.go

输出:

#
# command-line-arguments
#

mkdir -p $WORK/b001/
cat >$WORK/b001/_gomod_.go << 'EOF' # internal
package main
import _ "unsafe"
//go:linkname __debug_modinfo__ runtime.modinfo
var __debug_modinfo__ = "0w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nmod\tgoHelloWorld\t(devel)\t\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/usr/local/go/pkg/darwin_amd64/runtime.a
EOF
cd /Users/bytedance/goHelloWorld
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.16 -complete -buildid n6gyFYLsk3XgS57M4INh/n6gyFYLsk3XgS57M4INh -dwarf=false -goversion go1.16.4 -D _/Users/bytedance/goHelloWorld -importcfg $WORK/b001/importcfg -pack -c=4 ./main.go $WORK/b001/_gomod_.go
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=$WORK/b001/_pkg_.a
....
packagefile path=/usr/local/go/pkg/darwin_amd64/path.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/main -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=YVAr2monG5TnrAB0h8HG/n6gyFYLsk3XgS57M4INh/n6gyFYLsk3XgS57M4INh/YVAr2monG5TnrAB0h8HG -extld=clang $WORK/b001/_pkg_.a
$WORK/b001/exe/main

这里可以看到创建了两个临时文件夹 b001exe,先执行了 compile 命令,然后 link,生成了归档文件 _pkg_.a最终可执行文件 ,最终的可执行文件放在 exe 文件夹里面。命令的最后一步就是执行了可执行文件。

img

3. go run 配合 -work

举个例子,生成的临时文件可以用 go run -work 看到,比如当前生成的临时文件夹是如下的路径:

执行:

go run -work main.go

输出:

WORK=/var/folders/cg/0cgg4vd9495c_n3dxvv_df8w0000gp/T/go-build3905178702
Hello World!
Test go run

进入临时文件夹:

cd /var/folders/cg/0cgg4vd9495c_n3dxvv_df8w0000gp/T/go-build3905178702

查看目录结构:

$ tree
.
└── b001
    ├── _gomod_.go
    ├── _pkg_.a
    ├── exe
    │   └── main
    ├── importcfg
    └── importcfg.link

可以看到,最终go run命令是生成了 2 个文件

  • 一个是归档文件 _pkg_.a
  • 一个是可执行文件 main

go run 命令在第二次执行的时候,如果发现导入的代码包没有发生变化,那么 go run 不会再次编译这个导入的代码包。直接静态链接进来。

# 三、go build

go build 命令主要是用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。

  1. 如果是普通包,当你执行 go build命令后,不会产生任何文件;
  2. 如果是 main 包,当只执行 go build命令后,会在当前目录下生成一个可执行文件。如果需要在 $GOPATH/bin 目录下生成相应的 exe 文件,需要执行 go install 或者使用 go build -o 路径/可执行文件
  3. 如果某个文件夹下有多个文件,而你只想编译其中某一个文件,可以在 go build 之后加上文件名,例如 go build a.gogo build 命令默认会编译当前目录下的所有 go 文件;
  4. 你也可以指定编译输出的文件名。比如,我们可以指定 go build -o 可执行文件名,默认情况是你的 package 名(非 main 包),或者是第一个源文件的文件名(main 包);
  5. go build 会忽略目录下以 “_” 或者 “.” 开头的 go 文件;
  6. 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件;
  7. 当代码包中有且仅有一个命令源码文件的时候,在文件夹所在目录中执行 go build 命令,会在该目录下生成一个与目录同名的可执行文件;
# 当前目录
$ ll
total 16
-rw-r--r--  1 bytedance  staff   29  7 19 16:54 go.mod
-rw-r--r--  1 bytedance  staff  106  7 19 17:10 main.go

# 执行 go build
$ go build

# 再次查看当前目录,会发现多了 goHelloWorld,这个就是 go build 编译产物
$ ll
total 3984
-rw-r--r--  1 bytedance  staff       29  7 19 16:54 go.mod
-rwxr-xr-x  1 bytedance  staff  2027936  7 19 17:21 goHelloWorld
-rw-r--r--  1 bytedance  staff      106  7 19 17:10 main.go

Go 1.16 变化

Go 1.16 后,go build/run 命令不再自动更新 go.mod 和 go.sum。

下面来看看 go build 干了什么:

1. go build 配合 -n

执行:

go build -n

输出:

mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile goHelloWorld=/Users/bytedance/Library/Caches/go-build/71/71bdab3154f908f152290c268aabe0220943c6c9d64e40d85be2aa4d5170c47b-d
...
packagefile io=/usr/local/go/pkg/darwin_amd64/io.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=7YYz2SWVaL2TAgEq9Aju/S_ayM9h6okuR2tz0sOmx/_chCAYSxt9RhagjJjDzh/7YYz2SWVaL2TAgEq9Aju -extld=clang /Users/bytedance/Library/Caches/go-build/71/71bdab3154f908f152290c268aabe0220943c6c9d64e40d85be2aa4d5170c47b-d
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out goHelloWorld

可以看到,执行过程和 go run 大体相同,唯一不同的就是在最后一步:go run 是执行了可执行文件,但是 go build 命令,只是把库源码文件编译了一遍,然后把可执行文件移动到了当前目录的文件夹中。

img

# 四、go install

go install 命令是用来编译并安装代码包或者源码文件的。

执行:

go install -n

输出:

mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile goHelloWorld=/Users/bytedance/Library/Caches/go-build/71/71bdab3154f908f152290c268aabe0220943c6c9d64e40d85be2aa4d5170c47b-d
...
packagefile path=/usr/local/go/pkg/darwin_amd64/path.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=7YYz2SWVaL2TAgEq9Aju/S_ayM9h6okuR2tz0sOmx/_chCAYSxt9RhagjJjDzh/7YYz2SWVaL2TAgEq9Aju -extld=clang /Users/bytedance/Library/Caches/go-build/71/71bdab3154f908f152290c268aabe0220943c6c9d64e40d85be2aa4d5170c47b-d
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mkdir -p /Users/bytedance/go/bin/
mv $WORK/b001/exe/a.out /Users/bytedance/go/bin/goHelloWorld

go install 命令在内部实际上分成了两步操作:

  1. 生成结果文件(可执行文件或者 _pkg_.a 包)
    • 可执行文件: 一般是 go install 带 main 函数的 go 文件产生的,有函数入口,可以直接运行
    • _pkg_.a 包: 一般是 go install 不包含 main 函数的 go 文件产生的,没有函数入口,只能被调用
  2. 把编译好的结果移到 $GOPATH/pkg 或者 $GOPATH/bin

go install 用于编译并安装指定的代码包及它们的依赖包。当指定的代码包的依赖包还没有被编译和安装时,该命令会先去处理依赖包。与 go build 命令一样,传给 go install 命令的代码包参数应该以导入路径的形式提供。并且,go build 命令的绝大多数标记也都可以用于 go install

实际上,go install 命令只比 go build 命令多做了一件事,即:安装编译后的结果文件到指定目录。

img

注意:在安装多个库源码文件时有可能遇到如下的问题

$  go install envir.go fpath.go ipath.go pnode.go util.go
go install: no install location for .go files listed on command line (GOBIN not set)

而且,在我们为环境变量 GOBIN 设置了正确的值之后,这个错误提示信息仍然会出现。

这是因为,只有在安装命令源码文件的时候,命令程序才会将环境变量 GOBIN 的值作为结果文件的存放目录。而在安装库源码文件时,在命令程序内部的代表结果文件存放目录路径的那个变量不会被赋值。最后,命令程序会发现它依然是个无效的空值。

所以,命令程序会同样返回一个关于“无安装位置”的错误。

这就引出一个结论,我们只能使用安装代码包的方式来安装库源码文件,而不能在 go install 命令罗列并安装它们。

另外,go install 命令目前无法接受标记 -o 以自定义结果文件的存放位置。这也从侧面说明了 go install 命令不支持针对库源码文件的安装操作。

# 五、go get

go get 命令用于从远程代码仓库(比如 Github )上下载并安装代码包。

# 1. 常用选项

可选项 作用
-d 让命令程序只执行下载动作,而不执行安装动作。
-f 仅在使用-u标记时才有效。该标记会让命令程序忽略掉对已下载代码包的导入路径的检查。
如果下载并安装的代码包所属的项目是你从别人那里 fork 过来的,那么这样做就尤为重要了。
-fix 让命令程序在下载代码包后先执行修正动作,而后再进行编译和安装。
-insecure 允许命令程序使用非安全的 scheme(如 HTTP )去下载指定的代码包。
如果你用的代码仓库(如公司内部的 Gitlab )没有HTTPS 支持,可以添加此标记。请在确定安全的情况下使用它。
-t 让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
-u 让命令利用网络来更新已有代码包及其依赖包。
默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。

# 2. go get 做了什么

执行:

go get -x github.com/go-errors/errors

输出:

cd git clone https://gi thub. com go- errors/ errors /Users/bytedance/go/src/github.com/go-errors/errors
cd /Users/bytedance/go/src/github.com/go-errors/errors 
git submodule update --init --recursive
cd /Users/bytedance/go/src/github.com/go-errors/errors
git show-ref
cd /Users/bytedance/go/src/github.com/go-errors/errors
git submodule update --init --recursive
WORK=/var/folders/kt/nlhsnpgn6lgd_q16f8j83sbhØØØØgn/T/go-build188558329

这里可以很明显的看到,执行完 go get 命令以后,会调用 git clone 方法下载源码,并编译,最终会把库源码文件编译成归档文件安装到 pkg 对应的相关平台目录下。

img

# 3. 引入了 go module 后,go get 变化

使用go module之后,go get 拉取依赖的方式就发生了变化。

  • 拉取最新的版本(优先择取 tag)

    go get golang.org/x/text@latest
    
  • 拉取 master 分支的最新 commit

    go get golang.org/x/text@master
    
  • 拉取 tag 为 v0.3.2 的 commit

    go get golang.org/x/text@v0.3.2
    
  • 拉取 hash 为 342b231 的 commit,最终会被转换为其对应的 tag 号

    go get golang.org/x/text@342b2e
    
  • 拉取指定版本,如指定版本 v3

    go get github.com/smartwalle/alipay/v3
    
  • 更新

    go get -u
    

# 4. 注意

注意:由于不可诉原因,go get 经常拉不到代码

  1. 需要将下面两个变量加入到系统环境变量中:

    GO111MODULE=on
    GOPROXY=https://goproxy.io
    
  2. 覆盖原来的值

    go env -w GO111MODULE=on
    go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct
    

# 六、go clean

go clean 命令是用来移除当前源码包里面编译生成的文件。

这些文件包括:

文件 来源
_obj/ 旧的 object 目录,由 Makefiles 遗留
_test/ 旧的 test 目录,由 Makefiles 遗留
_testmain.go 旧的 gotest 文件,由 Makefiles 遗留
test.out 旧的 test 记录,由 Makefiles 遗留
build.out 旧的 test 记录,由 Makefiles 遗留
*.[568ao] object文件,由 Makefiles 遗留
DIR(.exe) 由 go build 产生
DIR.test(.exe) 由 go test -c 产生
MAINFILE(.exe) 由 go build MAINFILE.go产生

# 七、go fmt

go fmt 命令主要是用来帮你格式化所写好的代码文件。

比如我们写了一个格式很糟糕的 test.go 文件,我们只需要使用 fmt go test.go 命令,就可以让 go 帮我们格式化我们的代码文件。但是我们一般很少使用这个命令,因为我们的开发工具一般都带有保存时自动格式化功能,这个功能底层其实就是调用了 go fmt 命令而已。

使用 go fmt 命令,更多时候是用 gofmt,而且需要参数 -w,否则格式化结果不会写入文件。gofmt -w src,可以格式化整个项目。

# 八、go test

go test 命令,会自动读取源码目录下面名为 *_test.go 的文件,生成并运行测试用的可执行文件。

默认的情况下,不需要任何的参数,它会自动把你源码包下面所有 test 文件测试完毕,当然你也可以带上参数,详情请参考 go help test。

# 九、go doc

go doc 命令是一个很强大的文档工具。

如何查看相应 package 的文档呢?

  • 例如 builtin 包,那么执行 go doc builtin;
  • 如果是http包,那么执行 go doc net/http;
  • 查看某一个包里面的函数,那么执行 go doc fmt Printf;
  • 也可以查看相应的代码,执行 go doc -src fmt Printf;

go doc 对应的一个非常好用的工具 godoc,我们先下载:

go get golang.org/x/tools/cmd/godoc

通过命令在命令行执行

  • godoc -http=:端口号 (godoc 无空格)

比如

  • godoc -http=:8080

然后在浏览器中打开 127.0.0.1:8080,你将会看到一个 golang.org 的本地 copy 版本,通过它你可以查询 pkg 文档等其它内容。如果你设置了 GOPATH,在 pkg 分类下,不但会列出标准包的文档,还会列出你本地 GOPATH 中所有项目的相关文档,这对于经常被限制访问的用户来说是一个不错的选择。

image-20210719175744351

# 十、go fix

go fix 用来修复以前老版本的代码到新版本,例如 go1 之前老版本的代码转化到 go1。

# 十一、go version

查看当前 go 版本。

# 十二、go env

查看当前 go 的环境变量。

# 十三、go list

查看当前安装的全部的 package。

上次更新: 8/5/2021, 6:37:10 PM