go run 一步完成编译和运行,不保留二进制文件。go build 编译后在当前目录留下一个可执行文件。go install 编译后把可执行文件放进 $HOME/go/bin。三者都编译成原生机器码,没有哪个是在解释执行。
| 命令 | 编译 | 运行 | 保留二进制文件 | 二进制文件的位置 |
|---|---|---|---|---|
go run . | 是 | 是 | 否 | 临时目录,用完即删 |
go build | 是 | 否 | 是 | 当前目录(或 -o path) |
go install | 是 | 否 | 是 | $GOBIN,否则为 $GOPATH/bin |
下面这个程序就是本页示例中用到的程序。编辑器会像 go run 那样运行它。
go run
go run 接收一个包(通常是 .,即当前目录)或一组 .go 文件:
go run .
go run main.go
go run ./cmd/server
包后面的所有内容都会作为参数传给你的程序:
go run . --port 8080 verbose
version: dev
built for: linux/amd64
args: [--port 8080 verbose]
优先用 go run .,而不是 go run main.go。指定文件名只会编译那一个文件,所以一旦包里有了第二个文件,其中定义的函数就会被报告为 undefined。包与导入一页详细介绍了这个错误。
go run 还能直接运行某个远程程序的指定版本而无需安装,这对代码生成器很方便:
go run golang.org/x/tools/cmd/stringer@v0.30.0 -type=Color
go build
go build 编译当前目录下的包。对于 main 包,它会写出一个可执行文件;对于库包,它会编译、报告错误,然后丢弃结果。
go mod init example.com/hello
go build
ls
go.mod hello main.go
二进制文件的名字遵循以下规则:
| 你运行的命令 | 二进制文件名 |
|---|---|
在模块 example.com/hello 中运行 go build | hello |
go build ./cmd/server | server |
go build main.go | main(以第一个文件命名) |
go build -o bin/app . | bin/app |
以上任意一种,加上 GOOS=windows | 同样的名字再加 .exe |
还有两种你会经常用到的写法:
go build ./... # compile every package in the module
go vet ./... # static checks, covered below
./... 表示“这个目录及其下面的每个目录”。当有多个 main 包时,go build ./... 不会写出任何二进制文件;它是一种快速检查“是否全部都能编译”的方法。
常用的构建标志
| 标志 | 作用 |
|---|---|
-o name | 输出文件或目录 |
-v | 编译时打印包名 |
-race | 启用数据竞争检测器构建(更慢、二进制更大;用于测试) |
-trimpath | 从二进制文件中去掉本地文件系统路径,用于可复现构建 |
-ldflags "-s -w" | 去掉符号表和 DWARF 调试信息,让二进制文件更小 |
-ldflags "-X main.version=1.4.0" | 在链接时设置一个字符串变量 |
-tags name | 包含受 //go:build name 约束保护的文件 |
大多数 Go 项目都用 -X 标志把版本号写进二进制文件。它只对包级的 string 变量有效(对常量无效):
go build -o app -ldflags "-X main.version=1.4.0" .
./app
version: 1.4.0
built for: linux/amd64
args: []
二进制文件还会记录它的模块版本,从 Go 1.18 起还会记录构建时所基于的版本控制提交。go version -m ./app 会把它们打印出来,程序也可以用 runtime/debug.ReadBuildInfo 读取。
go install
go install 的构建方式和 go build 完全一样,然后把可执行文件移到 $GOBIN;如果没有设置 GOBIN,则移到 $GOPATH/bin(默认是 $HOME/go/bin):
go install .
go env GOPATH
它最常见的用途是安装用 Go 编写的工具。加上 @version,它会安装程序而不改动你的 go.mod:
go install golang.org/x/tools/gopls@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
如果安装后 shell 找不到这个工具,说明 $HOME/go/bin 不在你的 PATH 中。把 export PATH=$PATH:$(go env GOPATH)/bin 加到你的 shell 配置文件里。
用 GOOS 和 GOARCH 交叉编译
Go 可以在任何机器上为另一种操作系统或 CPU 构建程序,不需要额外的工具链。为构建命令设置两个环境变量即可:
GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 .
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .
GOOS=darwin GOARCH=arm64 go build -o app-macos .
GOOS=windows GOARCH=amd64 go build -o app.exe .
在 PowerShell 中,设置环境变量的方式有所不同:
$env:GOOS = "linux"; $env:GOARCH = "amd64"; go build -o app-linux .
最常用的组合:
| GOOS | GOARCH | 目标平台 |
|---|---|---|
linux | amd64 | 大多数服务器和容器 |
linux | arm64 | AWS Graviton、运行 64 位系统的树莓派 |
darwin | arm64 | Apple 芯片的 Mac |
darwin | amd64 | Intel 芯片的 Mac |
windows | amd64 | 64 位 Windows |
js | wasm | 浏览器中的 WebAssembly |
go tool dist list 会列出所有支持的组合(Go 1.24 中有 48 种)。
只有纯 Go 代码的交叉编译才这么简单。使用 cgo 的包(C 代码,比如某些 SQLite 驱动)需要针对目标平台的 C 交叉编译器。交叉编译时 cgo 默认是禁用的;显式设置 CGO_ENABLED=0 还能得到一个完全静态链接的 Linux 二进制文件,这正是极简的 scratch 或 distroless 容器镜像所需要的。
go fmt 与 go vet
有两项检查应该出现在每个工作流程以及 CI 中。
go fmt 会按照唯一的官方风格重写文件:制表符缩进、字段对齐、统一的空格。它是 gofmt -l -w 的一层封装:
go fmt ./...
gofmt -l . # list files that are not formatted; empty output means clean
go vet 会报告那些能编译、但几乎肯定有错的代码。Printf 格式不匹配就是典型的例子:
package main
import "fmt"
func main() {
count := 3
fmt.Printf("%s items\n", count)
}
go vet ./...
# example.com/hello
# [example.com/hello]
./main.go:7:2: fmt.Printf format %s has arg count of wrong type int
这个程序能编译,并打印 %!s(int=3) items,这正是 vet 要捕获的那类 bug。vet 的其他检查还包括:按值复制 sync.Mutex、无法到达的代码、语法错误的结构体标签,以及从未被调用的 context.CancelFunc。go test 会自动运行其中一部分检查;go build 一项都不运行。
其他 go 子命令
| 命令 | 用途 |
|---|---|
go test ./... | 运行测试 |
go mod tidy | 添加缺失的依赖并删除未使用的依赖 |
go get pkg@version | 添加或更改一个依赖 |
go clean -cache | 清空构建缓存 |
go env | 打印 Go 的配置 |
go doc fmt.Println | 在终端中显示文档 |
go list -m all | 列出构建中的每个模块 |
为什么第二次构建很快
Go 会把编译好的包缓存在构建缓存中(go env GOCACHE)。重新构建时,只有源码或依赖发生变化的包才会重新编译,所以对一个没有改动的项目执行 go run .,几乎瞬间就能启动。如果在更换 Go 版本或环境变量之后,构建出现了奇怪的行为,可以用 go clean -cache 清空缓存;不过你很少需要这么做。
常见问题
go run 和 go build 有什么区别?
go run 把程序编译到一个临时目录,运行它,然后丢掉二进制文件。go build 编译程序并把二进制文件写到当前目录,方便你再次运行或复制到别处。开发时用 go run,需要可执行文件时用 go build。
怎样设置 go build 的输出文件名?
用 -o:go build -o myapp . 会生成 myapp(在 Windows 上,要自己写 -o myapp.exe)。不加 -o 时,go build 用包导入路径的最后一段给二进制文件命名;如果你传入的是 .go 文件,就用第一个文件命名;为 Windows 构建时还会加上 .exe。
怎样把 Go 交叉编译到 Linux 或 Windows?
构建时设置 GOOS 和 GOARCH:GOOS=linux GOARCH=amd64 go build -o app-linux . 或 GOOS=windows GOARCH=amd64 go build .(会生成一个 .exe)。纯 Go 代码不需要额外的工具链。go tool dist list 会列出所有支持的组合。
go install 把二进制文件放在哪里?
如果设置了 $GOBIN,就放在那里,否则放在 $GOPATH/bin,默认是 $HOME/go/bin。把这个目录加入 PATH,就能直接按名字运行已安装的工具。go install example.com/tool@latest 会安装一个工具,而不会把它加入你的模块。
go build 会运行 go vet 吗?
不会。go build 只负责编译。go test 会自动运行 go vet 的一部分检查,但要运行全部检查,需要你自己执行 go vet ./...,通常放在 CI 中,和 gofmt -l . 一起运行。