網站首頁 編程語言 正文
導讀
由于Golang是編譯型語言(非腳本型語言),如果你想在Golang程序中獲取當前執行目錄將是一件非常蛋疼的事情。以前大家最折中的解決方案就是通過啟動傳參或是環境變量將路徑手動傳遞到程序,而今天我在看日志庫的時候發現了一種新的解決方案。
Go程序兩種不同的執行方式
用Go編寫的程序有兩種執行方式,go run和go build
通常的做法是go run用于本地開發,用一個命令中快速測試代碼確實非常方便;在部署生產環境時,我們會通過go build構建出二進制文件然后上傳到服務器再去執行。
兩種啟動方式會產生什么問題?
那么兩種啟動方式下,獲取到當前執行路徑會產生什么問題?
話不多說,我們直接上代碼
我們編寫獲取當前可執行文件路徑的方法
package main import ( "fmt" "log" "os" "path/filepath" ) func main() { fmt.Println("getCurrentAbPathByExecutable = ", getCurrentAbPathByExecutable()) } // 獲取當前執行程序所在的絕對路徑 func getCurrentAbPathByExecutable() string { exePath, err := os.Executable() if err != nil { log.Fatal(err) res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) return res
首先通過go run啟動
D:\Projects\demo>go run main.go
getCurrentAbPathByExecutable = C:\Users\XXX\AppData\Local\Temp\go-build216571510\b001\exe
再嘗試go build執行
D:\Projects\demo>go build & demo.exe
getCurrentAbPathByExecutable = D:\Projects\demo
通過對比執行結果,我們發現兩種執行方式,我們獲取到了不同的路徑。而且很明顯,go run獲取到的路徑是錯誤的。
原因: 這是由于go run會將源代碼編譯到系統TEMP或TMP環境變量目錄中并啟動執行;而go build只會在當前目錄編譯出可執行文件,并不會自動執行。
我們可以簡單理解為,go run main.go等價于go build & ./main
雖然兩種執行方式最終都是一樣的過程:源碼->編譯->可執行文件->執行輸出,但他們的執行目錄卻完全不一樣了。
新的方案誕生
這是在我今天查看服務日志(zap庫)的時候,突然反應過來一件事情。比如下面是一條簡單的日志,而服務是通過go run啟動的,但日志庫卻把我正確的程序路徑D:/Projects/te-server/modules/es/es.go:139給打印出來了
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源碼,發現是通過runtime.Caller()實現的,其實所有Golang日志庫都會有runtime.Caller()這個調用。
我開心的以為找到了最終答案,然后寫代碼試了下:
package main import ( "fmt" "path" "runtime" ) func main() { fmt.Println("getCurrentAbPathByCaller = ", getCurrentAbPathByCaller()) } // 獲取當前執行文件絕對路徑(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
嗯~~ 結果完全正確!
然后我再把構建好的程序扔到linux再運行后,它把我windows的路徑給打印出來了 --!
[root@server app]# chmod +x demo
[root@server app]# ./demo
getCurrentAbPathByCaller = D:/Projects/demo
沒想到白白高興一場,這個時候我就在想,既然go run時可以通過runtime.Caller()獲取到正確的結果,go build時也可以通過 os.Executable()來獲取到正確的路徑;
那如果我能判定當前程序是通過go run還是go build執行的,選擇不同的路徑獲取方法,所有問題不就迎刃而解了嗎。
區分程序是go run還是go build執行
Go沒有提供接口讓我們區分程序是go run還是go build執行,但我們可以換個思路來實現:
根據go run的執行原理,我們得知它會源代碼編譯到系統TEMP或TMP環境變量目錄中并啟動執行;
那我們可以直接在程序中對比os.Executable()獲取到的路徑是否與環境變量TEMP設置的路徑相同, 如果相同,說明是通過go run啟動的,因為當前執行路徑是在TEMP目錄;不同的話自然是go build的啟動方式。
下面是完整代碼:
package main import ( "fmt" "log" "os" "path" "path/filepath" "runtime" "strings" ) func main() { fmt.Println("getTmpDir(當前系統臨時目錄) = ", 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 // 獲取系統臨時目錄,兼容go run func getTmpDir() string { dir := os.Getenv("TEMP") if dir == "" { dir = os.Getenv("TMP") res, _ := filepath.EvalSymlinks(dir) return res // 獲取當前執行文件絕對路徑 func getCurrentAbPathByExecutable() string { exePath, err := os.Executable() if err != nil { log.Fatal(err) res, _ := filepath.EvalSymlinks(filepath.Dir(exePath)) // 獲取當前執行文件絕對路徑(go run) func getCurrentAbPathByCaller() string { var abPath string _, filename, _, ok := runtime.Caller(0) if ok { abPath = path.Dir(filename) return abPath
在windows執行
D:\Projects\demo>go run main.go
getTmpDir(當前系統臨時目錄) = 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(當前系統臨時目錄) = C:\Users\XXX\AppData\Local\Temp
getCurrentAbPathByExecutable(僅支持go build) = D:\Projects\demo
getCurrentAbPathByCaller(僅支持go run) = D:/Projects/demo
getCurrentAbPath(最終方案-全兼容) = D:\Projects\demo
在windows編譯后上傳到Linux執行
[root@server app]# pwd
/data/app
[root@server app]# ./demo
getTmpDir(當前系統臨時目錄) = .
getCurrentAbPathByExecutable(僅支持go build) = /data/app
getCurrentAbPathByCaller(僅支持go run) = D:/Projects/demo
getCurrentAbPath(最終方案-全兼容) = /data/app
對比結果,我們可以看到,在不同的系統中,不同的執行方式,我們封裝的getCurrentAbPath方法最終都輸出的正確的結果,perfect!
原文鏈接:https://www.cnblogs.com/jiftle/p/16177738.html
相關推薦
- 2022-04-25 利用Redis實現訪問次數限流的方法詳解_Redis
- 2022-09-08 python?logging模塊的分文件存放詳析_python
- 2022-07-01 Python查詢缺失值的4種方法總結_python
- 2022-04-22 小程序和網站中無限滾動的實現
- 2023-02-05 不同的編程語言輸出?“Hello?World”?代碼_其它綜合
- 2022-09-24 深入理解C#委托delegate的使用_C#教程
- 2022-09-07 python輸入、數據類型轉換及運算符方式_python
- 2022-08-12 virtualenv隔離Python環境的問題解析_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支