關(guān)于Golang獲取當(dāng)前項(xiàng)目絕對(duì)路徑的問(wèn)題
導(dǎo)讀
由于Golang是編譯型語(yǔ)言(非腳本型語(yǔ)言),如果你想在Golang程序中獲取當(dāng)前執(zhí)行目錄將是一件非常蛋疼的事情。以前大家最折中的解決方案就是通過(guò)啟動(dòng)傳參或是環(huán)境變量將路徑手動(dòng)傳遞到程序,而今天我在看日志庫(kù)的時(shí)候發(fā)現(xiàn)了一種新的解決方案。
Go程序兩種不同的執(zhí)行方式
用Go編寫(xiě)的程序有兩種執(zhí)行方式,go run和go build
通常的做法是go run用于本地開(kāi)發(fā),用一個(gè)命令中快速測(cè)試代碼確實(shí)非常方便;在部署生產(chǎn)環(huán)境時(shí),我們會(huì)通過(guò)go build構(gòu)建出二進(jìn)制文件然后上傳到服務(wù)器再去執(zhí)行。
兩種啟動(dòng)方式會(huì)產(chǎn)生什么問(wèn)題?
那么兩種啟動(dòng)方式下,獲取到當(dāng)前執(zhí)行路徑會(huì)產(chǎn)生什么問(wèn)題?
話不多說(shuō),我們直接上代碼
我們編寫(xiě)獲取當(dāng)前可執(zhí)行文件路徑的方法
package main import ( "fmt" "log" "os" "path/filepath" ) func main() { fmt.Println("getCurrentAbPathByExecutable = ", getCurrentAbPathByExecutable()) } // 獲取當(dāng)前執(zhí)行程序所在的絕對(duì)路徑 func getCurrentAbPathByExecutable() string { exePath, err := os.Executable() if err != nil { log.Fatal(err) res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) return res
首先通過(guò)go run啟動(dòng)
D:\Projects\demo>go run main.go getCurrentAbPathByExecutable = C:\Users\XXX\AppData\Local\Temp\go-build216571510\b001\exe
再嘗試go build執(zhí)行
D:\Projects\demo>go build & demo.exe getCurrentAbPathByExecutable = D:\Projects\demo
通過(guò)對(duì)比執(zhí)行結(jié)果,我們發(fā)現(xiàn)兩種執(zhí)行方式,我們獲取到了不同的路徑。而且很明顯,go run獲取到的路徑是錯(cuò)誤的。
原因: 這是由于go run會(huì)將源代碼編譯到系統(tǒng)TEMP或TMP環(huán)境變量目錄中并啟動(dòng)執(zhí)行;而go build只會(huì)在當(dāng)前目錄編譯出可執(zhí)行文件,并不會(huì)自動(dòng)執(zhí)行。
我們可以簡(jiǎn)單理解為,go run main.go等價(jià)于go build & ./main
雖然兩種執(zhí)行方式最終都是一樣的過(guò)程:源碼->編譯->可執(zhí)行文件->執(zhí)行輸出,但他們的執(zhí)行目錄卻完全不一樣了。
新的方案誕生
這是在我今天查看服務(wù)日志(zap庫(kù))的時(shí)候,突然反應(yīng)過(guò)來(lái)一件事情。比如下面是一條簡(jiǎn)單的日志,而服務(wù)是通過(guò)go run啟動(dòng)的,但日志庫(kù)卻把我正確的程序路徑D:/Projects/te-server/modules/es/es.go:139給打印出來(lái)了
2021-03-26 17:47:06 D:/Projects/te-server/modules/es/es.go:139 update es index {"index": "tags", "data": "[200 OK] {"acknowledged":true}"}
于是我馬上去翻看zap源碼,發(fā)現(xiàn)是通過(guò)runtime.Caller()實(shí)現(xiàn)的,其實(shí)所有Golang日志庫(kù)都會(huì)有runtime.Caller()這個(gè)調(diào)用。
我開(kāi)心的以為找到了最終答案,然后寫(xiě)代碼試了下:
package main import ( "fmt" "path" "runtime" ) func main() { fmt.Println("getCurrentAbPathByCaller = ", getCurrentAbPathByCaller()) } // 獲取當(dāng)前執(zhí)行文件絕對(duì)路徑(go run) func getCurrentAbPathByCaller() string { var abPath string _, filename, _, ok := runtime.Caller(0) if ok { abPath = path.Dir(filename) return abPath
首先在windows下面go run 和go build試一下
D:\Projects\demo>go run main.go getCurrentAbPathByCaller = D:/Projects/demo D:\Projects\demo>go build & demo.exe getCurrentAbPathByCaller = D:/Projects/demo
嗯~~ 結(jié)果完全正確!
然后我再把構(gòu)建好的程序扔到linux再運(yùn)行后,它把我windows的路徑給打印出來(lái)了 --!
[root@server app]# chmod +x demo [root@server app]# ./demo getCurrentAbPathByCaller = D:/Projects/demo
沒(méi)想到白白高興一場(chǎng),這個(gè)時(shí)候我就在想,既然go run時(shí)可以通過(guò)runtime.Caller()獲取到正確的結(jié)果,go build時(shí)也可以通過(guò) os.Executable()來(lái)獲取到正確的路徑;
那如果我能判定當(dāng)前程序是通過(guò)go run還是go build執(zhí)行的,選擇不同的路徑獲取方法,所有問(wèn)題不就迎刃而解了嗎。
區(qū)分程序是go run還是go build執(zhí)行
Go沒(méi)有提供接口讓我們區(qū)分程序是go run還是go build執(zhí)行,但我們可以換個(gè)思路來(lái)實(shí)現(xiàn):
根據(jù)go run的執(zhí)行原理,我們得知它會(huì)源代碼編譯到系統(tǒng)TEMP或TMP環(huán)境變量目錄中并啟動(dòng)執(zhí)行;
那我們可以直接在程序中對(duì)比os.Executable()獲取到的路徑是否與環(huán)境變量TEMP設(shè)置的路徑相同, 如果相同,說(shuō)明是通過(guò)go run啟動(dòng)的,因?yàn)楫?dāng)前執(zhí)行路徑是在TEMP目錄;不同的話自然是go build的啟動(dòng)方式。
下面是完整代碼:
package main import ( "fmt" "log" "os" "path" "path/filepath" "runtime" "strings" ) func main() { fmt.Println("getTmpDir(當(dāng)前系統(tǒng)臨時(shí)目錄) = ", getTmpDir()) fmt.Println("getCurrentAbPathByExecutable(僅支持go build) = ", getCurrentAbPathByExecutable()) fmt.Println("getCurrentAbPathByCaller(僅支持go run) = ", getCurrentAbPathByCaller()) fmt.Println("getCurrentAbPath(最終方案-全兼容) = ", getCurrentAbPath()) } // 最終方案-全兼容 func getCurrentAbPath() string { dir := getCurrentAbPathByExecutable() if strings.Contains(dir,getTmpDir()) { return getCurrentAbPathByCaller() return dir // 獲取系統(tǒng)臨時(shí)目錄,兼容go run func getTmpDir() string { dir := os.Getenv("TEMP") if dir == "" { dir = os.Getenv("TMP") res, _ := filepath.EvalSymlinks(dir) return res // 獲取當(dāng)前執(zhí)行文件絕對(duì)路徑 func getCurrentAbPathByExecutable() string { exePath, err := os.Executable() if err != nil { log.Fatal(err) res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) // 獲取當(dāng)前執(zhí)行文件絕對(duì)路徑(go run) func getCurrentAbPathByCaller() string { var abPath string _, filename, _, ok := runtime.Caller(0) if ok { abPath = path.Dir(filename) return abPath
在windows執(zhí)行
D:\Projects\demo>go run main.go getTmpDir(當(dāng)前系統(tǒng)臨時(shí)目錄) = C:\Users\XXX\AppData\Local\Temp getCurrentAbPathByExecutable(僅支持go build) = C:\Users\XXX\AppData\Local\Temp\go-build456189690\b001\exe getCurrentAbPathByCaller(僅支持go run) = D:/Projects/demo getCurrentAbPath(最終方案-全兼容) = D:/Projects/demo
D:\Projects\demo>go build & demo.exe getTmpDir(當(dāng)前系統(tǒng)臨時(shí)目錄) = C:\Users\XXX\AppData\Local\Temp getCurrentAbPathByExecutable(僅支持go build) = D:\Projects\demo getCurrentAbPathByCaller(僅支持go run) = D:/Projects/demo getCurrentAbPath(最終方案-全兼容) = D:\Projects\demo
在windows編譯后上傳到Linux執(zhí)行
[root@server app]# pwd /data/app [root@server app]# ./demo getTmpDir(當(dāng)前系統(tǒng)臨時(shí)目錄) = . getCurrentAbPathByExecutable(僅支持go build) = /data/app getCurrentAbPathByCaller(僅支持go run) = D:/Projects/demo getCurrentAbPath(最終方案-全兼容) = /data/app
對(duì)比結(jié)果,我們可以看到,在不同的系統(tǒng)中,不同的執(zhí)行方式,我們封裝的getCurrentAbPath方法最終都輸出的正確的結(jié)果,perfect!
到此這篇關(guān)于關(guān)于Golang獲取當(dāng)前項(xiàng)目絕對(duì)路徑的問(wèn)題的文章就介紹到這了,更多相關(guān)Golang絕對(duì)路徑內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Web框架Gin中間件實(shí)現(xiàn)原理步驟解析
這篇文章主要為大家介紹了Web框架Gin中間件實(shí)現(xiàn)原理步驟解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10深入分析golang多值返回以及閉包的實(shí)現(xiàn)
相對(duì)于C/C++,golang有很多新穎的特性,例如goroutine,channel等等,這些特性其實(shí)從golang源碼是可以理解其實(shí)現(xiàn)的原理。今天這篇文章主要來(lái)分析下golang多值返回以及閉包的實(shí)現(xiàn),因?yàn)檫@兩個(gè)實(shí)現(xiàn)golang源碼中并不存在,我們必須從匯編的角度來(lái)窺探二者的實(shí)現(xiàn)。2016-09-09Go語(yǔ)言學(xué)習(xí)之WaitGroup用法詳解
Go語(yǔ)言中的?WaitGroup?和?Java?中的?CyclicBarrier、CountDownLatch?非常類似。本文將詳細(xì)為大家講講WaitGroup的用法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-06-06深入剖析Go語(yǔ)言編程中switch語(yǔ)句的使用
這篇文章主要介紹了Go語(yǔ)言編程中switch語(yǔ)句的使用,是Go語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-10-10Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)
Go中的?channel?其實(shí)并沒(méi)有提供批量讀取數(shù)據(jù)的方法,需要我們自己實(shí)現(xiàn)一個(gè),使用本文就來(lái)為大家大家介紹一下如何通過(guò)Go?channel實(shí)現(xiàn)批量讀取數(shù)據(jù)吧2023-12-12Golang監(jiān)聽(tīng)日志文件并發(fā)送到kafka中
這篇文章主要介紹了Golang監(jiān)聽(tīng)日志文件并發(fā)送到kafka中,日志收集項(xiàng)目的準(zhǔn)備中,本文主要講的是利用golang的tail庫(kù),監(jiān)聽(tīng)日志文件的變動(dòng),將日志信息發(fā)送到kafka中?,需要的朋友可以參考一下2022-04-04