<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          來聊一聊,Go 的相對路徑問題

          共 4716字,需瀏覽 10分鐘

           ·

          2021-08-13 17:06

          大家好,我是煎魚。

          Go 語言中存在各種運行方式,如何正確的引用文件路徑成為一個值得商議的問題

          以我的一個老 Demo gin-blog 為例,當(dāng)我們在項目根目錄下運行。

          無論是執(zhí)行 go run main.go 時能夠正常運行,執(zhí)行 go build也是正常的。如下:

          [$ 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)
          ...

          在不同的目錄層級下,不同的方式運行,又是怎么樣的呢,帶著我們的疑問去學(xué)習(xí)!

          問題

          go run

          我們上移目錄層級,到 $GOPATH/src 下,執(zhí)行 go run gin-blog/main.go

          [$ 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

          使用 go build 命令,執(zhí)行 ./gin-blog/main。如下:

          [$ 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

          這時候你要打一個大大的問號,就是我的程序讀取到什么地方去了?

          我們通過分析得知,Go 運行的相對路徑是相對于執(zhí)行命令時的目錄,自然也就讀取不到了。

          思考

          既然已經(jīng)知道問題的所在點,我們就可以尋思做點什么 : )

          我們想到相對路徑是相對執(zhí)行命令的目錄,那么我們獲取可執(zhí)行文件的地址,拼接起來不就好了嗎?

          實踐

          我們編寫獲取當(dāng)前可執(zhí)行文件路徑的方法:

          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())

          我們分別執(zhí)行以下兩個命令,查看輸出結(jié)果。

          1、 go run

          $ go run main.go
          2018/03/12 18:45:40 /tmp/go-build962610262/b001/exe

          2、 go build

          $ ./main
          2018/03/12 18:49:44 $GOPATH/src/gin-blog

          剖析

          我們聚焦在 go run 的輸出結(jié)果上,發(fā)現(xiàn)它是一個臨時文件的地址,這是為什么呢?

          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 執(zhí)行時會將文件放到 /tmp/go-build... 目錄下,編譯并運行。

          因此go run main.go出現(xiàn)/tmp/go-build962610262/b001/exe結(jié)果也不奇怪了,因為它已經(jīng)跑到臨時目錄下去執(zhí)行可執(zhí)行文件了。

          思考

          這就已經(jīng)很清楚了,那么我們想想,會出現(xiàn)哪些問題呢。如下:

          • 依賴相對路徑的文件,出現(xiàn)路徑出錯的問題。
          • go rungo build 不一樣,一個到臨時目錄下執(zhí)行,一個可手動在編譯后的目錄下執(zhí)行,路徑的處理方式會不同。
          • 不斷go run,不斷產(chǎn)生新的臨時文件。

          這其實就是根本原因了,因為 go rungo build 的編譯文件執(zhí)行路徑并不同,執(zhí)行的層級也有可能不一樣,自然而然就出現(xiàn)各種讀取不到的奇怪問題了。

          解決方案

          一、獲取編譯后的可執(zhí)行文件路徑

          1、 將配置文件的相對路徑與GetAppPath()的結(jié)果相拼接,可解決go build main.go的可執(zhí)行文件跨目錄執(zhí)行的問題(如:./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 來補救。

          2、 通過傳遞參數(shù)指定路徑,可解決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"

          二、增加os.Getwd()進行多層判斷

          參見 beego 讀取 app.conf 的代碼。

          該寫法可兼容 go build 和在項目根目錄執(zhí)行 go run ,但是若跨目錄執(zhí)行 go run 就不行。

          三、配置全局系統(tǒng)變量

          我們可以通過os.Getenv來獲取系統(tǒng)全局變量,然后與相對路徑進行拼接。

          1、 設(shè)置項目工作區(qū)

          簡單來說,就是設(shè)置項目(應(yīng)用)的工作路徑,然后與配置文件、日志文件等相對路徑進行拼接,達到相對的絕對路徑來保證路徑一致。

          參見 gogs 讀取GOGS_WORK_DIR進行拼接的代碼。

          2、 利用系統(tǒng)自帶變量

          簡單來說就是通過系統(tǒng)自帶的全局變量,例如$HOME等,將配置文件存放在$HOME/conf/etc/conf下。

          這樣子就能更加固定的存放配置文件,不需要額外去設(shè)置一個環(huán)境變量。

          拓展

          go test 在一些場景下也會遇到路徑問題,因為go test只能夠在當(dāng)前目錄執(zhí)行,所以在執(zhí)行測試用例的時候,你的執(zhí)行目錄已經(jīng)是測試目錄了。

          需要注意的是,如果采用獲取外部參數(shù)的辦法,用 os.args 時,go test -argsgo run、go build 會有命令行參數(shù)位置的不一致問題。

          總結(jié)

          這三種解決方案,在目前可見的開源項目或介紹中都能找到這些的身影。優(yōu)缺點也是顯而易見的,我認為應(yīng)在不同項目選定合適的解決方案即可。

          建議大家不要強依賴讀取配置文件的模塊,應(yīng)當(dāng)將其“堆積木”化,需要什么配置才去注冊什么配置變量,可以解決一部分的問題。

          大家又有什么想法呢,一起討論一波?

          關(guān)注煎魚,吸取他的知識 ??



          你好,我是煎魚。高一折騰過前端,參加過國賽拿了獎,大學(xué)搞過 PHP。現(xiàn)在整 Go,在公司負責(zé)微服務(wù)架構(gòu)等相關(guān)工作推進和研發(fā)。

          從大學(xué)開始靠自己賺生活費和學(xué)費,到出版 Go 暢銷書《Go 語言編程之旅》,再到獲得 GOP(Go 領(lǐng)域最有觀點專家)榮譽,點擊藍字查看我的出書之路。

          日常分享高質(zhì)量文章,輸出 Go 面試、工作經(jīng)驗、架構(gòu)設(shè)計,加微信拉讀者交流群,記得點贊!

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码插入大大大大 | 欧美少妇坐爱视频 | 天堂色 | 天天射天天搞天天干 | 欧美日日 |