原文地址:聊一聊,Golang 「相對」路徑問題html
Golang
中存在各類運行方式,如何正確的引用文件路徑成爲一個值得商議的問題git
以 gin-blog 爲例,當咱們在項目根目錄下,執行 go run main.go
時可以正常運行(go build
也是正常的)github
[$ gin-blog]# go run main.go [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /api/v1/tags --> gin-blog/routers/api/v1.GetTags (3 handlers) ...
那麼在不一樣的目錄層級下,不一樣的方式運行,又是怎麼樣的呢,帶着咱們的疑問去學習golang
一、 go run
咱們上移目錄層級,到 $GOPATH/src
下,執行 go run gin-blog/main.go
segmentfault
[$ src]# go run gin-blog/main.go 2018/03/12 16:06:13 Fail to parse 'conf/app.ini': open conf/app.ini: no such file or directory exit status 1
二、 go build,執行 ./gin-blog/main
api
[$ src]# ./gin-blog/main 2018/03/12 16:49:35 Fail to parse 'conf/app.ini': open conf/app.ini: no such file or directory
這時候你要打一個大大的問號,就是個人程序讀取到什麼地方去了app
咱們經過分析得知,Golang
的相對路徑是相對於執行命令時的目錄;天然也就讀取不到了學習
既然已經知道問題的所在點,咱們就能夠尋思作點什麼 : )測試
咱們想到相對路徑是相對執行命令的目錄,那麼咱們獲取可執行文件的地址,拼接起來不就行了嗎?ui
咱們編寫獲取當前可執行文件路徑的方法
import ( "path/filepath" "os" "os/exec" "string" ) func GetAppPath() string { file, _ := exec.LookPath(os.Args[0]) path, _ := filepath.Abs(file) index := strings.LastIndex(path, string(os.PathSeparator)) return path[:index] }
將其放到啓動代碼處查看路徑
log.Println(GetAppPath())
咱們分別執行如下兩個命令,查看輸出結果
一、 go run
$ go run main.go 2018/03/12 18:45:40 /tmp/go-build962610262/b001/exe
二、 go build
$ ./main 2018/03/12 18:49:44 $GOPATH/src/gin-blog
咱們聚焦在 go run
的輸出結果上,發現它是一個臨時文件的地址,這是爲何呢?
在go help run
中,咱們能夠看到
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.
也就是 go run
執行時會將文件放到 /tmp/go-build...
目錄下,編譯並運行
所以go run main.go
出現/tmp/go-build962610262/b001/exe
結果也不奇怪了,由於它已經跑到臨時目錄下去執行可執行文件了
這就已經很清楚了,那麼咱們想一想,會出現哪些問題呢
go run
和 go build
不同,一個到臨時目錄下執行,一個可手動在編譯後的目錄下執行,路徑的處理方式會不一樣go run
,不斷產生新的臨時文件這其實就是根本緣由了,由於 go run
和 go build
的編譯文件執行路徑並不一樣,執行的層級也有可能不同,天然而然就出現各類讀取不到的奇怪問題了
1、獲取編譯後的可執行文件路徑
一、 將配置文件的相對路徑與GetAppPath()
的結果相拼接,可解決go build main.go
的可執行文件跨目錄執行的問題(如:./src/gin-blog/main
)
import ( "path/filepath" "os" "os/exec" "string" ) func GetAppPath() string { file, _ := exec.LookPath(os.Args[0]) path, _ := filepath.Abs(file) index := strings.LastIndex(path, string(os.PathSeparator)) return path[:index] }
可是這種方式,對於go run
依舊無效,這時候就須要2來補救
二、 經過傳遞參數指定路徑,可解決go run
的問題
package main import ( "flag" "fmt" ) func main() { var appPath string flag.StringVar(&appPath, "app-path", "app-path") flag.Parse() fmt.Printf("App path: %s", appPath) }
運行
go run main.go --app-path "Your project address"
2、增長os.Getwd()
進行多層判斷
參見 beego 讀取 app.conf
的代碼
該寫法可兼容 go build
和在項目根目錄執行 go run
,可是若跨目錄執行 go run
就不行
3、配置全局系統變量
咱們能夠經過os.Getenv
來獲取系統全局變量,而後與相對路徑進行拼接
一、 設置項目工做區
簡單來講,就是設置項目(應用)的工做路徑,而後與配置文件、日誌文件等相對路徑進行拼接,達到相對的絕對路徑來保證路徑一致
參見 gogs 讀取GOGS_WORK_DIR
進行拼接的代碼
二、 利用系統自帶變量
簡單來講就是經過系統自帶的全局變量,例如$HOME
等,將配置文件存放在$HOME/conf
或/etc/conf
下
這樣子就能更加固定的存放配置文件,不須要額外去設置一個環境變量
(這點今早與一位SFer討論了一波,感謝)
go test
在一些場景下也會遇到路徑問題,由於go test
只可以在當前目錄執行,因此在執行測試用例的時候,你的執行目錄已是測試目錄了
須要注意的是,若是採用獲取外部參數的辦法,用 os.args
時,go test -args
和 go run
、go build
會有命令行參數位置的不一致問題
這三種解決方案,在目前可見的開源項目或介紹中都能找到這些的身影
優缺點也是顯而易見的,我認爲應在不一樣項目選定合適的解決方案便可
建議你們不要強依賴讀取配置文件的模塊,應當將其「堆積木」化,須要什麼配置纔去註冊什麼配置變量,能夠解決一部分的問題
你們又有什麼想法呢,在SF 這裏 討論一波?