go语言常用技巧(go命令速查手册)
go语言常用技巧(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\tcode\t(devel)\t\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2" EOF cat >$W
写在前面go语言开发者在日常工作或学习中,用到最多的命令可能是go build、go run、go install、go get...,通过这些命令可以帮我们编译程序、运行程序、安装程序和获取代码包,然而在执行这些命令的时候,你是否思考过go编译过程中是如何组织我们的源码文件的?go install的时候发生了什么?以及go get只是去下载我们依赖的包文件吗?go还有哪些实用的命令?这是一篇可以说比较基础的文章,也可以作为go命令的一个速查手册,旨在帮助我们更深刻的了解go。
几个概念- 命令源码文件:声明为属于main代码包,并且包含无参数声明和结果声明的main函数的源码文件,一般一个项目只有一个命令源码文件。
- 库源码文件:没有命令源码的特征,存在于某个包里面的普通源码文件
- 测试源码文件:以_test.go为后缀的源码文件,且必须包含Test或Beanchmark为名称前缀的函数。
gopath可能是每个go开发者最熟悉不过的东西了,gopath就是我们的工作区,通过go env可以查看到我们的gopath,如 GOPATH="/Users/code/go",gopath就是我们的工作区,gopath下面一般会建立bin、src、pkg三个文件夹。
- bin:go install后的可执行文件会放在bin目录里。但是在go的环境变量中有个叫GOBIN的,默认情况下GOBIN是空的,但是当我们设置了GOBIN后,GOPATH/bin就会没有意义了,go install的可执行文件会默认放到GOBIN下。
export GOBIN=/tmp //声明GOBIN目录
go install $GOPATH/src/code //安装code程序
/tmp/code 发现程序已经被安装到了GOBIN下
- src:我们编写的源码文件都在这个文件夹里。
- pkg:依赖包安装后的库源码文件存放的位置,即归档文件(.a结尾),也就是程序编译后生成的静态库文件。
go有一些辅助命令可以帮助我们更好的理解go的一些执行过程,辅助命令的体现就是go run/build.. -x xx.go -x就是辅助命令。-x可以有以下类型:
- -a:强行对所有涉及到的代码包(包括标准库中的代码包)进行重新构建,即使它们已经是最新的了。
- -n:打印构建期间所用到的其它命令,但是并不真正执行它们,通过-n可以观察构建的过程
- -race:用于检测数据竞争问题,比如map并发读写...
- -v:用于打印命令执行过程中涉及的代码包,包括了我们依赖的代码包,还可以看到依赖的代码包关联的代码包。
- -work:用于打印命令执行时生成和使用的临时工作目录名字,且命令执行完后不删除它。如果不添加此标记,临时工作目录会在命令执行完后被删除。
- -x:打印命令执行过程中用到的所有命令,并同时执行他们。
- -p n:构建的并行数量(n)。默认情况下并行数量与CPU数量相同。
- -o:编译指定输出到的文件。
- -asmflags:此标记可以后跟另外一些标记,如-D、-I、-S等。这些后跟的标记用于控制Go语言编译器编译汇编语言文件时的行为。
- -buildmode:指定编译模式,使用方式如-buildmode=default,主要控制编译器在编译完成后生成静态链接库(即.a文件,也就是我们之前说的归档文件)、动态链接库(即.so文件)或/和可执行文件(在Windows下是.exe文件)。
- -compiler:指定当前使用的编译器的名称,其值可以为gc或GCCgo。其中,gc编译器即为Go语言自带的编辑器,而gccgo编译器则为GCC提供的Go语言编译器。
- -gccgoflags:指定需要传递给gccgo编译器或链接器的标记的列表。
- -gcflags:指定需要传递给go tool compile命令的标记的列表。
- -installsuffix:为了使当前的输出目录与默认的编译输出目录分离,可以使用这个标记。此标记的值会作为结果文件的父目录名称的后缀。其实,如果使用了-race标记,这个标记会被自动追加且其值会为race。如果我们同时使用了-race标记和-installsuffix,那么在-installsuffix标记的值的后面会再被追加_race,并以此来作为实际使用的后缀。
- -ldflags:指定需要传递给go tool link命令的标记的列表。
- -linkshared:用于与-buildmode=shared一同使用。后者会使作为编译目标的非main代码包都被合并到一个动态链接库文件中,而前者则会在此之上进行链接操作。
- pkgdir:可以指定一个目录。编译器会只从该目录中加载代码包的归档文件,并会把编译可能会生成的代码包归档文件放置在该目录下。
- -tags:指定在实际编译期间需要受理的编译标签(也可被称为编译约束)的列表。
- -toolexec:可以让我们去自定义在编译期间使用一些Go语言自带工具(如vet、asm等)的方式。
- -mod:使用-mod [mode]模式,[mode]支持readonly,release,vendor,当执行go build -mod=vendor的时候,会在生成可执行文件的同时将项目的依赖包放到主模块的vendor目录下。
- -modcacherw:将下载的mod的包以读写方式保留在模块缓存中而不是使其只读。
- -trimpath:删除二进制文件中的源码路径信息,比如当我们的程序panic的时候,可能把相关错误的源码路径打印出来,通过这个可以隐藏。
我们通过go run -n main.go来看下构建的过程
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile command-line-arguments=/Users/gopher/Library/Caches/go-build/f5/f58206d3722b7787f6341e5013f38f3887e44138ffbdafbb07b67da377632762-d
packagefile code/utils=/Users/gopher/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.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=ttUV5epeZDG7q3yVlG2A/ek8iSJ8rjD-RiujnYKAd/hNomEMXuvdA3I1ks6XOE/ttUV5epeZDG7q3yVlG2A -extld=clang /Users/goper/Library/Caches/go-build/f5/f58206d3722b7787f6341e5013f38f3887e44138ffbdafbb07b67da377632762-d
$WORK/b001/exe/main
- 创建临时目录:$WORK/b001/
- 查找依赖包信息:$WORK/b001/importcfg.link
- 创建exe目录:$WORK/b001/exe/
- 通过go的工具tool的link来链接库文件(importcfg.link)生成可执行文件$WORK/b001/exe/main
通过 go run -work main.go,我们来看看临时文件夹:
go run -work main.go
WORK=/var/folders/s4/2cpbmp4s1_j4y3zv2s08m9q40000gn/T/go-build281107053
切到临时目录:
└── b001
├── exe
│ └── main
└── importcfg.link
- exe:就是我们的可执行文件。
- importcfg.link:我们程序依赖的包,通过链接器链接依赖包。
默认情况下,go run命令运行完后会删除临时文件夹。
go buildgo build用于编译我们的程序,默认编译后的文件存放在当前的文件夹中,如果指定-o那么就可以移动到指定的文件中。
我们通过go build -n main.go来看下build的过程:
#
# 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\tcode\t(devel)\t\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2"
EOF
cat >$WORK/b001/importcfg << 'EOF' # internal
# import config
packagefile code/utils=/Users/sunkang/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.a
packagefile runtime=/Users/sunkang/Library/Caches/go-build/b4/b44856e241a6bb3baf596eb19e4566e956a490ef403c1ed31ba8f014542fcf81-d
EOF
cd /Users/gopher/go/src/code
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.15 -complete -buildid Fjkl2yr7MirhGqbO0lrl/Fjkl2yr7MirhGqbO0lrl -goversion go1.15.3 -D _/Users/sunkang/go/src/code -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 code/utils=/Users/sunkang/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d
packagefile fmt=/usr/local/go/pkg/darwin_amd64/fmt.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=s0BcVGdGaAeuHGFL7teJ/Fjkl2yr7MirhGqbO0lrl/Fjkl2yr7MirhGqbO0lrl/s0BcVGdGaAeuHGFL7teJ -extld=clang $WORK/b001/_pkg_.a
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out main
整体流程和go run差不多,唯一不同的是在compile和link之后,生成的可执行文件会移动到当前文件夹中,并不是随着临时文件夹一起消亡。
go installgo install用于编译并安装指定的代码包及它们的依赖包,当指定的代码包的依赖包还没编译安装的时候,会先去安装依赖包。与go build不同的是,go install会把编译后的安装包放在指定的文件夹中。安装的代码包会在当前工作区的pkg目录下,即.a的归档文件,当我们没有设置GOBIN时,安装的命令源码文件会存放在当前工作区的bin目录下,当我们设置了GOBIN时,则会放在GOBIN下。 假设现在项目是这样的:
├── go.mod
├── main.go
└── utils
└── utils.go
main.go就是我们的入口文件,即命令源码文件,utils是我们的依赖包,即库源码文件。 当我们只在当前目录执行go install -n main.go后:
mkdir -p $WORK/b001/
cat >$WORK/b001/importcfg.link << 'EOF' # internal
...
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=yfU8nbngCa6KbgUlJLKa/Fjkl2yr7MirhGqbO0lrl/khh18opCAdXA909bR95q/yfU8nbngCa6KbgUlJLKa -extld=clang /Users/sunkang/Library/Caches/go-build/ed/ed5868e69c99c66b8bf4b399e989ea410063b44143b546fd3f4a98f758d73a47-d
/usr/local/go/pkg/tool/darwin_amd64/buildid -w $WORK/b001/exe/a.out # internal
mkdir -p /Users/gopher/go/bin/
mv $WORK/b001/exe/a.out /Users/gopher/go/bin/main
可以发现最后一行与go build的不同的是,它把可执行文件移动到了GOPATH/bin中。
当我们切到依赖包utils文件中执行 go install -n后:
mkdir -p $WORK/b001/
mkdir -p /Users/gopher/go/pkg/darwin_amd64/code/
mv /Users/gopher/Library/Caches/go-build/89/8931b87411d98f32ea6222ad605f3367700be896828286782db3e51de73217ff-d /Users/gopher/go/pkg/darwin_amd64/code/utils.a
可以发现会把依赖包安装到 GOPATH/pkg下,并且命名为.a结尾的归档文件。darwin_amd64是$GOOS_$GOARCH的拼装,即操作系统和处理器架构,code就是我们的项目名。
/Users/gopher/go/pkg/darwin_amd64/code
└── utils.a
所以go install大概流程是这样的:
go get通过go get 命令我们可以下载并安装一个依赖包,go get下载的源码文件会放在GOPATH中的第一个工作区。由于go在1.11开始支持go mod,所以当我们开启go mod后,通过go get获取的代码会下载在GOPAT/pkg/mod中,go get后面支持可选参数
- -d:让命令程序只执行下载动作,而不执行安装动作。
- -u:让命令利用网络来更新已有代码包及其依赖包。默认情况下,该命令只会从网络上下载本地不存在的代码包,而不会更新已有的代码包。
- -t:让命令程序同时下载并安装指定的代码包中的测试源码文件中依赖的代码包。
- -insecure:允许命令程序使用非安全的scheme(如HTTP)去下载指定的代码包。如果你用的代码仓库(如公司内部的Gitlab)没有HTTPS支持,可以添加此标记。请在确定安全的情况下使用它。
- -v:显示执行的命令。
go从1.11开始支持go mod模式,现在相信大家也基本都在使用go mod,相比vendor的好处,使用go mod之后,你的代码可以存在在任何位置。
- GO111MODULE=on 通过这个环境变量来开启go mod模式
- GOPROXY="https://goproxy.io",推荐大家用这个proxy来下载go的第三方依赖包。
- GOPRIVE="git.xx.xx",prive是私有的仓库,一些比如公司自己内部仓库,可以配置下私有仓库的域名来获取内部代码
go mod相关命令
- go mod init 模块名 初始化一个go项目
- go mod download 下载modules到本地cache,即$GOPATH/pkg/mod/cache/download中,执行go mod download -json可以以json格式输出信息:
//go mod download -json
{
"Path": "github.com/go-basic/uuid"
"Version": "v1.0.0"
"Info": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.info"
"GoMod": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.mod"
"Zip": "/Users/gopher/go/pkg/mod/cache/download/github.com/go-basic/uuid/@v/v1.0.0.zip"
"Dir": "/Users/gopher/go/pkg/mod/github.com/go-basic/uuid@v1.0.0"
"Sum": "h1:Faqtetcr8uwOzR2qp8RSpkahQiv4 BnJhrpuXPOo63M="
"GoModSum": "h1:yVtVnsXcmaLc9F4Zw7hTV7R0 vtuQw00mdXi F6tqco="
}
- go mod edit
编辑go.mod文件 选项有-json、-replace...,可以使用帮助go help mod edit,比如说如果你要修改某个包,可以直接使用 go mod edit -replace=old[@v]=new[@v],一般都是直接编辑go.mod文件了,这个命令用的不多。
- go mod graph
以文本模式打印依赖的包,比如我的go.mod是
module code
go 1.15
require (
github.com/gin-gonic/gin v1.7.4 // indirect
github.com/go-basic/uuid v1.0.0 // indirect
)
这时执行go mod graph
//go mod graph
code github.com/gin-gonic/gin@v1.7.4
code github.com/go-basic/uuid@v1.0.0
github.com/gin-gonic/gin@v1.7.4 github.com/gin-contrib/sse@v0.1.0
github.com/gin-gonic/gin@v1.7.4 github.com/go-playground/validator/v10@v10.4.1
github.com/gin-gonic/gin@v1.7.4 github.com/golang/protobuf@v1.3.3
github.com/gin-gonic/gin@v1.7.4 github.com/json-iterator/go@v1.1.9
github.com/gin-gonic/gin@v1.7.4 github.com/mattn/go-isatty@v0.0.12
github.com/gin-gonic/gin@v1.7.4
...
golang.org/x/text@v0.3.2 golang.org/x/tools@v0.0.0-20180917221912-90fa682c2a6e
发现还是看不出依赖关系,这里推荐大家使用 go get -u github.com/PaulXu-cn/go-mod-graph-chart/gmchart这个包,来查看依赖关系:
- go mod tidy
添加丢失或移出不需要的模块。
当前我的go.mod里面有个uuid的包,但是我的代码并没有引用。
module code
go 1.15
require github.com/go-basic/uuid v1.0.0 // indirect
执行go mod tidy:
module code
go 1.15
会发现帮我移除了不需要的包。
6.go mod verify
验证依赖是否正确。
7. go mod why
解释为什么需要包和模块,比如执行: go mod why github.com/go-basic/uuid,然后输出:
# github.com/go-basic/uuid
code/utils
github.com/go-basic/uuid
我的理解是 code/utils这个包有用到github.com/go-basic/uuid。
go.sumgo.sum文件的作用就两个:
- checksum,防止包被篡改,校验和
- 记录包的更新记录
当我们go get某个包的时候,会先下载到本地$GOPATH/pkg/mod/cache/download中,下载下来后会有一个名为vx.x.x.zip的压缩包,以及vx.x.x.ziphash的文件,vx.x.x.ziphash内容就是vx.x.x.zip经过hash的值,比如: h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0 /LYZDA=% 以uuid包为例子:当我们go get github.com/go-basic/uuid后,除了会在go.mod里追加一条require命令后,还会在go.sum里面写入两条记录:
github.com/go-basic/uuid v1.0.0 h1:Faqtetcr8uwOzR2qp8RSpkahQiv4 BnJhrpuXPOo63M=
github.com/go-basic/uuid v1.0.0/go.mod h1:yVtVnsXcmaLc9F4Zw7hTV7R0 vtuQw00mdXi F6tqco=
第一条hash就是我们上面提到的zip压缩包的hash值,第二条hash是如果我们的依赖包中有go.mod,那么就是这条go.mod的hash值。在准备把两条hash值记录更新到go.sum中的时候,为了确保依赖包的真实可靠性,go在下载完依赖包后,会通过go的环境变量GOSUMDB="sum.golang.org"指向的服务器去检查依赖包的hash值,如果查询的hash值和本地的hash值不一样,那么就拒绝向下执行,也不会更新go.sum。
go clean- -n:把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的。
- -i:清除关联的安装的包和可运行文件。我们可以通过结合-n来看看-i是如何执行的:
//go clean -i -n
cd /Users/gopher/go/job // 当前项目
rm -f job job.exe job.test job.test.exe main main.exe
rm -f /Users/gopher/go/bin/job
先切到我们的项目中去,然后尝试删除当前目录下的一些编译文件如.exe、.test等,最后去尝试删除$GOPATH/bin/job这个因为go install产生的编译文件。
- -r: 循环的清除在import中引入的包:
// go clean -r -n
cd /Users/gopher/go/job
rm -f job job.exe job.test job.test.exe main main.exe
cd /usr/local/go/src/fmt
rm -f fmt.test fmt.test.exe
cd /usr/local/go/src/errors
rm -f errors.test errors.test.exe
cd /usr/local/go/src/internal/reflectlite
rm -f reflectlite.test reflectlite.test.exe
....
job项目依赖很多包,这些依赖的包也会执行删除一些当前目录的编译文件。
- -x:打印出来执行的详细命令,其实就是-n打印的执行版本。
- -cache: 删除所有go build命令的缓存。
go build的过程是产生一些缓存的,这些缓存是存在go的环境变量GOCACHE中的 -cache就是删除相关的缓存的:
//go clean -n -cache
rm -r /Users/gopher/Library/Caches/go-build/00
/Users/gopher/Library/Caches/go-build/01
/Users/gopher/Library/Caches/go-build/02
/Users/gopher/Library/Caches/go-build/03
/Users/gopher/Library/Caches/go-build/04
...
rm -r /Users/gopher/Library/Caches/go-build/ff
- -testcache:删除当前包所有的测试结果。
当我们使用go test .来跑某个路径下面的测试用例时,会编译并测试路径下每个测试文件,并且会缓存测试结果,以避免不必要的重复测试,当缓存成功后,第二次跑test 会发现有个cached标识。
第一次
go test .
ok job 0.431s
第二次
go test .
ok job (cached)
这时候通过go clean -testcache 就是删除对应的测试缓存。
go clean -testcache
go test .
ok job 0.459s
- -modcache:删除go.mod模式下的缓存文件。
当我们启动go.mod模式来组织我们的go代码时,下载的依赖包会放在$GOPATH/pkg/mod中,通过 go clean -modcache就是删除$GOPATH/pkg/mod下的所有文件。
//go clean -n -modcache
rm -rf /Users/gopher/go/pkg/mod
结语
写本文的目的主要是因为自己知识的匮乏,自己用go也两年了,然而对go的一些命令、go的编译过程还是存在模糊感的,比如项目一直使用的go.mod,go.mod是如何管理我们依赖的代码包的,go.sum是什么?为什么需要go.sum?go build的过程发生了什么?另一方面本文也列举了基本日常开发够用的命令,这样当自己需要查找命令不用去网上百度了,可以作为一个速查手册。
也许我们日常工作任务不太需要我们对底层知识的了解,但是保持好奇心也是一个程序员快速进步的一种方式。原理、底层这些很枯燥的东西,如果能啃下来,会发现很多问题都想通了。就好比练武功,武功高强的人,会发现他们的内功心法很强大,当内功心法强大了,学什么武功也就快了。
欢迎大家关注公众号《假装懂编程》,我将持续输出网络、数据库、go、缓存、架构、面试、程序人生相关文章。