GoLang 命令
目錄
檢視可用命令
直接在終端中輸入 go help 即可顯示所有的 go 命令以及相應命令功能簡介,主要有下面這些:
- build: 編譯包和依賴
- clean: 移除物件檔案
- doc: 顯示包或者符號的文件
- env: 列印go的環境資訊
- bug: 啟動錯誤報告
- fix: 執行go tool fix
- fmt: 執行gofmt進行格式化
- generate: 從processing source生成go檔案
- get: 下載並安裝包和依賴
- install: 編譯並安裝包和依賴
- list: 列出包
- run: 編譯並執行go程式
- test: 執行測試
- tool: 執行go提供的工具
- version: 顯示go的版本
- vet: 執行go tool vet
命令的使用方式為: go command [args]
, 除此之外,可以使用go help <command>
在執行 go help 時,不僅僅列印了這些命令的基本資訊,還給出了一些概念的幫助資訊:
- c: Go和c的相互呼叫
- buildmode: 構建模式的描述
- filetype: 檔案型別
- gopath: GOPATH環境變數
- environment: 環境變數
- importpath: 匯入路徑語法
- packages: 包列表的描述
- testflag: 測試符號描述
- testfunc: 測試函式描述
同樣使用 go help <topic>
來檢視這些概念的的資訊。
build 和 run 命令
就像其他靜態型別語言一樣,要執行 go 程式,需要先編譯,然後在執行產生的可執行檔案。go build
命令就是用來編譯 go程式生成可執行檔案的。但並不是所有的 go 程式都可以編譯生成可執行檔案的, 要生成可執行檔案,go程式需要滿足兩個條件:
- 該go程式需要屬於main包
- 在main包中必須還得包含main函式
也就是說go程式的入口就是 main.main
, 即main包下的main函式, 例子(hello.go):
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
編譯hello.go,然後執行可執行程式:
$ go run hello.go # 將會生成可執行檔案 hello
$ ./hello # 執行可執行檔案
Hello World!
go build+檔案列表:
go build file1.go file2.go……
可編譯同目錄的多個原始碼檔案,go build 會編譯這些原始碼,輸出可執行檔案
go build編譯時的附加引數
go build 還有一些附加引數,可以顯示更多的編譯資訊和更多的操作,詳見下表所示。
附加引數 | 備 注 |
---|---|
-v | 編譯時顯示包名 |
-p n | 開啟併發編譯,預設情況下該值為 CPU 邏輯核數 |
-a | 強制重新構建 |
-n | 列印編譯時會用到的所有命令,但不真正執行 |
-x | 列印編譯時會用到的所有命令 |
-race | 開啟競態檢測 |
上面就是 go build 的基本用法,另外如果使用 go build 編譯的不是一個可執行程式,而是一個包,那麼將不會生成可執行檔案。
而 go run
命令可以將上面兩步併為一步執行(不會產生中間檔案)。
$ go run hello.go
Hello World!
注意:go run
不會在執行目錄下生成任何檔案,可執行檔案被放在臨時檔案中被執行,工作目錄被設定為當前目錄。在 go run 的後部可以新增引數,這部分引數會作為程式碼可以接受的命令列輸入提供給程式。
go run 不能使用“go run+包”的方式進行編譯,如需快速編譯執行包,需要使用如下步驟來代替:
- 使用 go build 生成可執行檔案。
- 執行可執行檔案。
clent命令
這個命令是用來移除當前原始碼包和關聯原始碼包裡面編譯生成的檔案。這些檔案包括
_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產生
*.so 由 SWIG 產生
我一般都是利用這個命令清除編譯檔案,然後github遞交原始碼,在本機測試的時候這些編譯檔案都是和系統相關的,但是對於原始碼管理來說沒必要。
$ go clean -i -n
cd /Users/astaxie/develop/gopath/src/mathapp
rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe
rm -f /Users/astaxie/develop/gopath/bin/mathapp
附加引數 | 備註 |
---|---|
-i | 清除關聯的安裝的包和可執行檔案,也就是通過go install安裝的檔案 |
-n | 把需要執行的清除命令打印出來,但是不執行,這樣就可以很容易的知道底層是如何執行的 |
-r | 迴圈的清除在import中引入的包 |
-x | 打印出來執行的詳細命令,其實就是-n列印的執行版本 |
fmt 和 doc 命令
go 語言有一個褒貶不一的特性,就是對格式的要求很嚴格,我是很喜歡這個特性的,因為可以保持程式碼的清晰一致,編譯組合開發,並且go還提供了一個非常強大的工具來格式化程式碼,它就是 go fmt sourcefile.go
, 不過通常其實不需要我們手動呼叫,各種編輯器都可以幫助我們自動完成格式化。
go doc
命令可以方便我們快速檢視包文件,go doc package
命令將會在終端中打印出指定 package 的文件。
另外有一個與 go doc
命令相關的命令是 godoc
, 可以通過它啟動我們自己的文件伺服器:
godoc -http=:8080
然後我們就可與在瀏覽器localhost:8080
中檢視go文件了
get 命令
go get 可以藉助程式碼管理工具通過遠端拉取或更新程式碼包及其依賴包,並自動完成編譯和安裝。整個過程就像安裝一個 App 一樣簡單。
使用 go get 前,需要安裝與遠端包匹配的程式碼管理工具,如 Git、SVN、HG 等,引數中需要提供一個包名。
遠端包的路徑格式
Go 語言的程式碼被託管於 Github.com 網站,該網站是基於 Git 程式碼管理工具的,很多有名的專案都在該網站託管程式碼。其他類似的託管網站還有 code.google.com、bitbucket.org 等。
這些網站的專案包路徑都有一個共同的標準,參見下圖所示
圖中的遠端包路徑是 Go 語言的原始碼,這個路徑共由 3 個部分組成:
- 網站域名:表示程式碼託管的網站,類似於電子郵件 @ 後面的伺服器地址。
- 作者或機構:表明這個專案的歸屬,一般為網站的使用者名稱,如果需要找到這個作者下的所有專案,可以直接在網站上通過搜尋“域名/作者”進行檢視。這部分類似於電子郵件 @ 前面的部分。
- 專案名:每個網站下的作者或機構可能會同時擁有很多的專案,圖中標示的部分表示專案名稱。
go get+遠端包
預設情況下,go get 可以直接使用。例如,想獲取 go 的原始碼並編譯,使用下面的命令列即可:
$ go get github.com/davyxu/cellnet
獲取前,請確保 GOPATH 已經設定。Go 1.8 版本之後,GOPATH 預設在使用者目錄的 go 資料夾下。
cellnet 只是一個網路庫,並沒有可執行檔案,因此在 go get 操作成功後 GOPATH 下的 bin 目錄下不會有任何編譯好的二進位制檔案。
需要測試獲取並編譯二進位制的,可以嘗試下面的這個命令。當獲取完成後,就會自動在 GOPATH 的 bin 目錄下生成編譯好的二進位制檔案。
$ go get github.com/davyxu/tabtoy
go get使用時的附加引數
使用 go get 時可以配合附加引數顯示更多的資訊及實現特殊的下載和安裝操作,詳見下表所示。
附加引數 | 附加引數 |
---|---|
-v | 顯示操作流程的日誌及資訊,方便檢查錯誤 |
-u | 下載丟失的包,但不會更新已經存在的包 |
-d | 只下載,不安裝 |
-insecure | 允許使用不安全的 HTTP 方式進行下載操作 |
install 命令
go install 的功能和 go build 類似,附加引數絕大多數都可以與 go build 通用。go install 只是將編譯的中間檔案放在 GOPATH 的 pkg 目錄下,以及固定地將編譯結果放在 GOPATH 的 bin 目錄下。
go install 的編譯過程有如下規律:
- go install 是建立在 GOPATH 上的,無法在獨立的目錄裡使用 go install。
- GOPATH 下的 bin 目錄放置的是使用 go install 生成的可執行檔案,可執行檔案的名稱來自於編譯時的包名。
- go install 輸出目錄始終為 GOPATH 下的 bin 目錄,無法使用-o附加引數進行自定義。
- GOPATH 下的 pkg 目錄放置的是編譯期間的中間檔案。
test命令
Go 語言擁有一套單元測試和效能測試系統,僅需要新增很少的程式碼就可以快速測試一段需求程式碼。
效能測試系統可以給出程式碼的效能資料,幫助測試者分析效能問題。
提示
單元測試(unit testing),是指對軟體中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般要根據實際情況去判定其具體含義,如C語言中單元指一個函式,Java 裡單元指一個類,圖形化的軟體中可以指一個視窗或一個選單等。總的來說,單元就是人為規定的最小的被測功能模組。
單元測試是在軟體開發過程中要進行的最低級別的測試活動,軟體的獨立單元將在與程式的其他部分相隔離的情況下進行測試。
單元測試——測試和驗證程式碼的框架
要開始一個單元測試,需要準備一個 go 原始碼檔案,在命名檔案時需要讓檔案必須以_test
結尾。
單元測試原始碼檔案可以由多個測試用例組成,每個測試用例函式需要以Test為字首,例如:
func TestXXX( t *testing.T )
- 測試用例檔案不會參與正常原始碼編譯,不會被包含到可執行檔案中。
- 測試用例檔案使用 go test 指令來執行,沒有也不需要 main() 作為函式入口。所有在以_test結尾的原始碼內以Test開頭的函式會自動被執行。
- 測試用例可以不傳入 *testing.T 引數。
package code11_3
import "testing"
func TestHelloWorld(t *testing.T) {
t.Log("hello world")
}
程式碼說明如下:
- 第 5 行,單元測試檔案 (*_test.go) 裡的測試入口必須以 Test 開始,引數為 *testing.T 的函式。一個單元測試檔案可以有多個測試入口。
- 第 6 行,使用 testing 包的 T 結構提供的 Log() 方法列印字串。
1) 單元測試命令列
單元測試使用 go test 命令啟動,例如:
$ go test helloworld_test.go
ok command-line-arguments 0.003s
$ go test -v helloworld_test.go
=== RUN TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
helloworld_test.go:8: hello world
PASS
ok command-line-arguments 0.004s
程式碼說明如下:
- 第 1 行,在 go test 後跟 helloworld_test.go 檔案,表示測試這個檔案裡的所有測試用例。
- 第 2 行,顯示測試結果,ok 表示測試通過,command-line-arguments 是測試用例需要用到的一個包名,0.003s 表示測試花費的時間。
- 第 3 行,顯示在附加引數中添加了-v,可以讓測試時顯示詳細的流程。
- 第 4 行,表示開始執行名叫 TestHelloWorld 的測試用例。
- 第 5 行,表示已經執行完 TestHelloWorld 的測試用例,PASS 表示測試成功。
- 第 6 行列印字串 hello world。
2) 執行指定單元測試用例
go test 指定檔案時預設執行檔案內的所有測試用例。可以使用-run
引數選擇需要的測試用例單獨執行,參考下面的程式碼。
package code11_3
import "testing"
func TestA(t *testing.T) {
t.Log("A")
}
func TestAK(t *testing.T) {
t.Log("AK")
}
func TestB(t *testing.T) {
t.Log("B")
}
func TestC(t *testing.T) {
t.Log("C")
}
這裡指定 TestA 進行測試:
$ go test -v -run TestA select_test.go
=== RUN TestA
--- PASS: TestA (0.00s)
select_test.go:6: A
=== RUN TestAK
--- PASS: TestAK (0.00s)
select_test.go:10: AK
PASS
ok command-line-arguments 0.003s
TestA 和 TestAK 的測試用例都被執行,原因是-run跟隨的測試用例的名稱支援正則表示式,使用-run TestA$即可只執行 TestA 測試用例。
3) 標記單元測試結果
當需要終止當前測試用例時,可以使用 FailNow,參考下面的程式碼。
func TestFailNow(t *testing.T) {
t.FailNow()
}
還有一種只標記錯誤不終止測試的方法,程式碼如下:
func TestFail(t *testing.T) {
fmt.Println("before fail")
t.Fail()
fmt.Println("after fail")
}
測試結果如下:
=== RUN TestFail
before fail
after fail
--- FAIL: TestFail (0.00s)
FAIL
exit status 1
FAIL command-line-arguments 0.002s
從日誌中看出,第 5 行呼叫 Fail() 後測試結果標記為失敗,但是第 7 行依然被程式執行了。
4) 單元測試日誌
每個測試用例可能併發執行,使用 testing.T 提供的日誌輸出可以保證日誌跟隨這個測試上下文一起列印輸出。testing.T 提供了幾種日誌輸出方法,詳見下表所示。
單元測試框架提供的日誌方法
方 法 | 備 注
---|---
Log | 列印日誌,同時結束測試
Logf | 格式化列印日誌,同時結束測試
Error | 列印錯誤日誌,同時結束測試
Errorf | 格式化列印錯誤日誌,同時結束測試
Fatal | 列印致命日誌,同時結束測試
Fatalf | 格式化列印致命日誌,同時結束測試
開發者可以根據實際需要選擇合適的日誌。
基準測試——獲得程式碼記憶體佔用和執行效率的效能資料
基準測試可以測試一段程式的執行效能及耗費 CPU 的程度。Go 語言中提供了基準測試框架,使用方法類似於單元測試,使用者無須準備高精度的計時器和各種分析工具,基準測試本身即可以打印出非常標準的測試報告。
1) 基礎測試基本使用
下面通過一個例子來了解基準測試的基本使用方法。
package code11_3
import "testing"
func Benchmark_Add(b *testing.B) {
var n int
for i := 0; i < b.N; i++ {
n++
}
}
這段程式碼使用基準測試框架測試加法效能。第 7 行中的 b.N 由基準測試框架提供。測試程式碼需要保證函式可重入性及無狀態,也就是說,測試程式碼不使用全域性變數等帶有記憶性質的資料結構。避免多次運行同一段程式碼時的環境不一致,不能假設 N 值範圍。
使用如下命令列開啟基準測試:
$ go test -v -bench=. benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4 20000000 0.33 ns/op
PASS
ok command-line-arguments 0.700s
程式碼說明如下:
- 第 1 行的
-bench=.
表示執行 benchmark_test.go 檔案裡的所有基準測試,和單元測試中的-run類似。 - 第 4 行中顯示基準測試名稱,2000000000 表示測試的次數,也就是 testing.B 結構中提供給程式使用的 N。“0.33 ns/op”表示每一個操作耗費多少時間(納秒)。
注意:Windows 下使用 go test 命令列時,-bench=.
應寫為-bench="."
。
2) 基準測試原理
基準測試框架對一個測試用例的預設測試時間是 1 秒。開始測試時,當以 Benchmark 開頭的基準測試用例函式返回時還不到 1 秒,那麼 testing.B 中的 N 值將按 1、2、5、10、20、50……遞增,同時以遞增後的值重新呼叫基準測試用例函式。
3) 自定義測試時間
通過-benchtime
引數可以自定義測試時間,例如:
$ go test -v -bench=. -benchtime=5s benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4 10000000000 0.33 ns/op
PASS
ok command-line-arguments 3.380s
4) 測試記憶體
基準測試可以對一段程式碼可能存在的記憶體分配進行統計,下面是一段使用字串格式化的函式,內部會進行一些分配操作。
func Benchmark_Alloc(b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", i)
}
}
在命令列中新增-benchmem
引數以顯示記憶體分配情況,參見下面的指令:
$ go test -v -bench=Alloc -benchmem benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Alloc-4 20000000 109 ns/op 16 B/op 2 allocs/op
PASS
ok command-line-arguments 2.311s
程式碼說明如下:
- 第 1 行的程式碼中-bench後添加了 Alloc,指定只測試 Benchmark_Alloc() 函式。
- 第 4 行程式碼的“16 B/op”表示每一次呼叫需要分配 16 個位元組,“2 allocs/op”表示每一次呼叫有兩次分配。
開發者根據這些資訊可以迅速找到可能的分配點,進行優化和調整
5) 控制計時器
有些測試需要一定的啟動和初始化時間,如果從 Benchmark() 函式開始計時會很大程度上影響測試結果的精準性。testing.B 提供了一系列的方法可以方便地控制計時器,從而讓計時器只在需要的區間進行測試。我們通過下面的程式碼來了解計時器的控制。
func Benchmark_Add_TimerControl(b *testing.B) {
// 重置計時器
b.ResetTimer()
// 停止計時器
b.StopTimer()
// 開始計時器
b.StartTimer()
var n int
for i := 0; i < b.N; i++ {
n++
}
}
從 Benchmark() 函式開始,Timer 就開始計數。StopTimer() 可以停止這個計數過程,做一些耗時的操作,通過 StartTimer() 重新開始計時。ResetTimer() 可以重置計數器的資料。
計數器內部不僅包含耗時資料,還包括記憶體分配的資料。
test命令小總結
go test package //生成並執行測試該原始碼包下面所有_test檔案下所有測試方法直到測試完畢
go test *_test.go //生成並執行測試該檔案下所有測試方法直到測試完畢
go test -v *_test.go //測試該檔案,並顯示測試的詳細命令
go test -v -bench=. *_test.go //執行相應的benchmarks方法,例如 -bench=.執行所有
go test -cover *_test.go //開啟測試覆蓋率
go test -run regexp *_test.go //正則匹配,例如 -run=Array 那麼就執行包含有Array開頭的函式
go test -v -run Test_A *_test.go //自定義測試方法
go test -v -bench=. -benchtime=5s *_test.go //自定義測試時間
go test -v -bench=Alloc -benchmem b*_test.go //測試記憶體,本例為對指定方法進行測試並測試記憶體
go test -cpuprofile cpu.out *_test.go //輸出cpu效能分析檔案
go test -memprofile mem.out *_test.go //輸出記憶體效能分析檔案
go test -blockprofile block.out *_test.go //輸出內部goroutine阻塞的效能分析檔案
pprof命令
Go 語言工具鏈中的 go pprof 可以幫助開發者快速分析及定位各種效能問題,如 CPU 消耗、記憶體分配及阻塞分析。
效能分析首先需要使用 runtime.pprof 包嵌入到待分析程式的入口和結束處。runtime.pprof 包在執行時對程式進行每秒 100 次的取樣,最少採樣 1 秒。然後將生成的資料輸出,讓開發者寫入檔案或者其他媒介上進行分析。
go pprof 工具鏈配合 Graphviz 圖形化工具可以將 runtime.pprof 包生成的資料轉換為 PDF 格式,以圖片的方式展示程式的效能分析結果
安裝第三方圖形化顯式分析資料工具(Graphviz)
Graphviz 是一套通過文字描述的方法生成圖形的工具包。描述文字的語言叫做 DOT。
在 www.graphviz.org(http://www.graphviz.org)網站可以獲取到最新的 Graphviz 各平臺的安裝包。
CentOS 下,可以使用 yum 指令直接安裝:
$ yum install graphiviz
安裝第三方效能分析來分析程式碼包
runtime.pprof 提供基礎的執行時分析的驅動,但是這套介面使用起來還不是太方便,例如:
- 輸出資料使用 io.Writer 介面,雖然擴充套件性很強,但是對於實際使用不夠方便,不支援寫入檔案。
- 預設配置項較為複雜。
很多第三方的包在系統包 runtime.pprof 的技術上進行便利性封裝,讓整個測試過程更為方便。這裡使用 github.com/pkg/profile 包進行例子展示,使用下面程式碼安裝這個包:
$ go get github.com/pkg/profile
效能分析程式碼
下面程式碼故意製造了一個性能問題,同時使用 github.com/pkg/profile 包進行效能分析。
package main
import (
"github.com/pkg/profile"
"time"
)
func joinSlice() []string {
var arr []string
for i := 0; i < 100000; i++ {
// 故意造成多次的切片新增(append)操作, 由於每次操作可能會有記憶體重新分配和移動, 效能較低
arr = append(arr, "arr")
}
return arr
}
func main() {
// 開始效能分析, 返回一個停止介面
stopper := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
// 在main()結束時停止效能分析
defer stopper.Stop()
// 分析的核心邏輯
joinSlice()
// 讓程式至少執行1秒
time.Sleep(time.Second)
}
程式碼說明如下:
- 第 4 行,引用 github.com/pkg/profile 第三方包封裝。
- 第 14 行,為了進行效能分析,這裡在已知元素大小的情況下,還是使用 append() 函式不斷地新增切片。效能較低,在實際中應該避免,這裡為了效能分析,故意這樣寫。
- 第 22 行,使用 profile.Start 呼叫 github.com/pkg/profile 包的開啟效能分析介面。這個 Start 函式的引數都是可選項,這裡需要指定的分析專案是 profile.CPUProfile,也就是 CPU 耗用。profile.ProfilePath(".") 指定輸出的分析檔案路徑,這裡指定為當前資料夾。profile.Start() 函式會返回一個 Stop 介面,方便在程式結束時結束效能分析。
- 第 25 行,使用 defer,將效能分析在 main() 函式結束時停止。
- 第 28 行,開始執行分析的核心。
- 第 31 行,為了保證效能分析資料的合理性,分析的最短時間是 1 秒,使用 time.Sleep() 在程式結束前等待 1 秒。如果你的程式預設可以執行 1 秒以上,這個等待可以去掉。
效能分析需要可執行配合才能生成分析結果,因此使用命令列對程式進行編譯,程式碼如下:
$ go run cpu.go
$ go tool pprof --pdf cpu.pprof > cpu.pdf
程式碼說明如下:
第 1 行執行 cpu.go ,在當前目錄輸出 cpu.pprof 檔案。
第 2 行,使用 go tool 工具鏈輸入 cpu.pprof ,生成 PDF 格式的輸出檔案,將輸出檔案重定向為 cpu.pdf 檔案。這個過程中會呼叫 Graphviz 工具,Windows 下需將 Graphviz 的可執行目錄新增到環境變數 PATH 中。
最終生成 cpu.pdf 檔案,使用 PDF 檢視器開啟檔案,觀察後發現下圖所示的某個地方可能存在瓶頸。
func joinSlice() []string {
const count = 100000
var arr []string = make([]string, count)
for i := 0; i < count; i++ {
arr[i] = "arr"
}
return arr
}
程式碼說明如下:
- 第 5 行,將切片預分配 count 個數量,避免之前使用 append() 函式的多次分配。
- 第 8 行,預分配後,直接對每個元素進行直接賦值。
重新執行上面的程式碼進行效能分析,最終得到的 cpu.pdf 中將不會再有耗時部分。
其他命令
其他命令不會經常使用,這裡就不介紹了,真的用到的時候,直接使用 go help command
即可檢視相關命令。