Talk about Golang’s “Relative” Path Problem

Preface

GolangThere are various modes of operation in, howCorrect reference file pathBecomes a problem worth discussing.

In order togin-blogFor example, when we are in the root directory of a project, executego run main.goCan run normally (go buildIt is also normal)

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

So what is it like to operate in different ways under different directory levels and learn with our doubts?

Problem

1、 go run
We move up the directory level to$GOPATH/srcNext, executego 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

2. go build, execute./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

At this time, you have to make a big question mark, that is, where did my program read it


We know from analysis that,GolangThe relative path of is relative to the directory where the command was executed.; Nature also can’t read

Thinking

Now that we know where the problem lies, we can think of something to do:)

We think that the relative path is the directory where the commands are executed relatively, so we can get the addresses of the executable files and join them together.

Practice

We wroteMethod for obtaining path of current executable file

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]
}

Place it at the startup code to view the path

log.Println(GetAppPath())

We execute the following two commands respectively to view the output results
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

analyse

We focus ongo runOn the output result of, it was found that it was the address of a temporary file. why?

Ingo help runWe can see that

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.

That is,go runThe file will be placed on the/tmp/go-build ...Directory, compile and run

thereforego run main.goAppear/tmp/go-build962610262/b001/exeThe result is not surprising, because it has already run to the temporary directory to execute the executable file.


This is already very clear, so let’s think about it, what problems will arise

  • A path error occurred for files that depend on relative paths.
  • go runAndgo buildDifferent, one can be executed in a temporary directory and the other can be executed manually in a compiled directory. The path will be handled differently.
  • Constantlygo run, continuously produce new temporary files

This is actuallyRoot causeYes, becausego runAndgo buildThe execution paths of the compiled files are not the same, and the execution levels may also be different, which naturally leads to various strange problems that cannot be read.

Solution

(1) obtaining the compiled executable file path

1, the relative path of the configuration file andGetAppPath()The results of the splicing can be solvedgo build main.goThe executable of the cross directory execution problems (such as:./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]
}

But this way, forgo runIs still invalid, this time need 2 to remedy

2, by passing parameters to specify the path, can solvego runThe problem of

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

run

go run main.go --app-path "Your project address"

II. Increaseos.Getwd()Making multi-layer judgment

Seebeegoreadapp.confThe code of

This writing is compatiblego buildAnd execute in that root directory of the projectgo runHowever, if it is executed across directoriesgo runNot at all

Third, configure global system variables

We can pass.os.GetenvTo obtain the global variables of the system, and then splice with the relative paths

1, set up the project workspace

In short, it is to set the working path of the project (application) and then splice it with the relative paths such as configuration files and log files to achieve the relative absolute path to ensure the path is consistent.

SeegogsreadGOGS_WORK_DIRCode for splicing

2. Use the system’s own variables

Simply put, it is through the system’s own global variables, such as$HOMEWait, store the configuration file in$HOME/confOr/etc/confunder

In this way, configuration files can be stored more firmly.There is no need to set an additional environment variable

(This was discussed with an SFer this morning. Thank you)

Expand

go testThere are also path problems in some scenarios becausego testCan only be executed in the current directory, so when executing test cases, your execution directory is already the test directory.

It should be noted that if you use the method of obtaining external parameters, use theos.argsAt that time,go test -argsAndgo rungo buildThere will be inconsistent positions of command line parameters.

Summary

These three solutions can be found in currently visible open source projects or introductions.

The advantages and disadvantages are also obvious, I think should be inChoose the right solution for different projectsJust do it

It is suggested that you do not rely heavily on modules that read configuration files, but should “pile up” them.What configuration is required to register what configuration variables, can solve some of the problems

What do you think, in SFHereA wave of discussions?