golang初探與命令源碼分析
- 2019 年 10 月 18 日
- 筆記
前段時間有群友在群里問一個go語言的問題:
就是有一個main.go的main函數里調用了另一個demo.go里的hello()函數。其中main.go和hello.go同屬於main包。但是在main.go的目錄下執行go run main.go卻報hello函數沒有定義的錯:
程式碼結構如下:
**gopath ---- src** **----gohello** **----hello.go** ** ----main.go**
main.go如下:
package main import "fmt" func main() { fmt.Println("my name is main") hello() }
hello.go如下:
package main import "fmt" func hello() { fmt.Println("my name is hello") }
當時我看了以為是他GOPATH配置的有問題,然後自己也按照這樣試了一下,報同樣的錯,在網上查了,也有兩篇文章是關於這個錯的,也提供了解決方法,即用go run main.go hello.go,試了確實是可以的。
雖然是個很簡單的問題,但是也涉及到了go語言底層對於命令行參數的解析。那就來分析一下語言底層的實現吧,看一下底層做了什麼,為什麼報這個錯?
分析:
以下使用到的Go SDK版本為1.8.3
該版本中go支援的基本命令有以下16個:
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
在Go SDK的src/cmd/go包下有main.go文件中,Command類型的commands數組對該16個命令提供了支援:
我們首先知道go語言的初始化流程如下:
在執行main.go中的主函數main之前,對import進來的包按順序初始化,最後初始化main.go中的類型和變數,當初始化到commands數組時,由於cmdRun定義在於main.go同包下的run.go中,那麼就先去初始化run.go中的變數和init方法,如下程式碼,先把cmdRun初始化為Command類型,然後執行init()函數。
var cmdRun = &Command{ UsageLine: "run [build flags] [-exec xprog] gofiles... [arguments...]", Short: "compile and run Go program", Long: ` Run compiles and runs the main package comprising the named Go source files. A Go source file is defined to be a file ending in a literal ".go" suffix. By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. If the -exec flag is given, 'go run' invokes the binary using xprog: 'xprog a.out arguments...'. If the -exec flag is not given, GOOS or GOARCH is different from the system default, and a program named go_$GOOS_$GOARCH_exec can be found on the current search path, 'go run' invokes the binary using that program, for example 'go_nacl_386_exec a.out arguments...'. This allows execution of cross-compiled programs when a simulator or other execution method is available. For more about build flags, see 'go help build'. See also: go build. `, } func init() { cmdRun.Run = runRun // break init loop addBuildFlags(cmdRun) cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "") }
init()中,將runRun(其實類型是一個方法,用於處理run後的參數)賦值給cmdRu.run,addBuildFlags(cmdRun)主要是給run後面增加命令行參數(如:-x是列印其執行過程中用到的所有命令,同時執行它們)。其他15個命令和cmdRun類似,各有各的run實現。
下來主要看main.go中main的這塊程式碼:
for _, cmd := range commands { if cmd.Name() == args[0] && cmd.Runnable() { cmd.Flag.Usage = func() { cmd.Usage() } if cmd.CustomFlags { args = args[1:] } else { cmd.Flag.Parse(args[1:]) args = cmd.Flag.Args() } cmd.Run(cmd, args) exit() return } }
這塊程式碼遍歷commands數組,當遍歷到cmdRun時,cmd.Name()其實就是拿到cmdRun.UsageLine的第一個單詞run
就會進入if分支,由於cmd.CustomFlags沒有初始化故為false,走else分支,然後開始解析args命令行參數,args[1:]即取run後的所有參數。然後去執行cmd.Run(cmd, args),對於cmdRun來說,這裡執行的就是run.go中init()的第一行賦值cmdRun.run(上面說了,這是一個函數,不同命令實現方式不同),即去執行run.go中的runRun函數,該函數主要是將命令行參數當文件去處理,如果是_test為後綴的,即測試文件,直接報錯。如果是目錄也直接報錯(而且go run後面只能包含一個含main函數的go文件)。注意到有這麼一行:
p := goFilesPackage(files)
goFilesPackage(files)除了校驗文件類型和後綴,還會入棧,載入,出棧等操作,由於啟動的時候沒有傳遞hello.go,所以系統載入main.go時找不到hello函數,導致報錯。
本公眾號免費提供csdn下載服務,海量IT學習資源,如果你準備入IT坑,勵志成為優秀的程式猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大數據、面試資料、前端 等資源。同時我們組建了一個技術交流群,裡面有很多大佬,會不定時分享技術文章,如果你想來一起學習提高,可以公眾號後台回復【2】,免費邀請加技術交流群互相學習提高,會不定期分享編程IT相關資源。
掃碼關注,精彩內容第一時間推給你