如何打开与 GOPATH 相关的文件?

我使用 io/ioutil来读取一个小文本文件:

fileBytes, err := ioutil.ReadFile("/absolute/path/to/file.txt")

这个很好用,但是这个不能随身携带。在我的示例中,要打开的文件位于 GOPATH 中,例如:

/Users/matt/Dev/go/src/github.com/mholt/mypackage/data/file.txt

由于 data文件夹紧挨着源代码,我想只指定相对路径:

data/file.txt

但是我得到了这个错误:

Open data/file.txt: 没有这样的文件或目录

如何使用它们的相对路径打开文件,特别是如果它们与我的 Go 代码放在一起?

(请注意,我的问题是关于相对于 GOPATH 打开文件的。在 Go 中使用任意相对路径打开文件就像给出相对路径而不是绝对路径一样简单,文件是相对于编译后的二进制工作目录打开的。在我的示例中,我希望打开相对于编译二进制文件的位置的文件。事后看来,这是一个糟糕的设计决策。)

97782 次浏览

Hmm... the path/filepath package has Abs() which does what I need (so far) though it's a bit inconvenient:

absPath, _ := filepath.Abs("../mypackage/data/file.txt")

Then I use absPath to load the file and it works fine.

Note that, in my case, the data files are in a package separate from the main package from which I'm running the program. If it was all in the same package, I'd remove the leading ../mypackage/. Since this path is obviously relative, different programs will have different structures and need this adjusted accordingly.

If there's a better way to use external resources with a Go program and keep it portable, feel free to contribute another answer.

I wrote gobundle to solve exactly this problem. It generates Go source code from data files, which you then compile into your binary. You can then access the file data through a VFS-like layer. It's completely portable, supports adding entire file trees, compression, etc.

The downside is that you need an intermediate step to build the Go files from the source data. I usually use make for this.

Here's how you'd iterate over all files in a bundle, reading the bytes:

for _, name := range bundle.Files() {
r, _ := bundle.Open(name)
b, _ := ioutil.ReadAll(r)
fmt.Printf("file %s has length %d\n", name, len(b))
}

You can see a real example of its use in my GeoIP package. The Makefile generates the code, and geoip.go uses the VFS.

I think Alec Thomas has provided The Answer, but in my experience it isn't foolproof. One problem I had with compiling resources into the binary is that compiling may require a lot of memory depending on the size of your assets. If they're small, then it's probably nothing to worry about. In my particular scenario, a 1MB font file was causing compilation to require somewhere around 1GB of memory to compile. It was a problem because I wanted it to be go gettable on a Raspberry Pi. This was with Go 1.0; things may have improved in Go 1.1.

So in that particular case, I opt to just use the go/build package to find the source directory of the program based on the import path. Of course, this requires that your targets have a GOPATH set up and that the source is available. So it isn't an ideal solution in all cases.

this seems to work pretty well:

import "os"
import "io/ioutil"


pwd, _ := os.Getwd()
txt, _ := ioutil.ReadFile(pwd+"/path/to/file.txt")

Starting from Go 1.16, you can use the embed package. This allows you to embed the files in the running go program.

Given the file structure:

-- main.go
-- data
\- file.txt

You can reference the file using a go directive

package main


import (
"embed"
"fmt"
)


//go:embed data/file.txt
var content embed.FS


func main() {
text, _ := content.ReadFile("data/file.txt")
fmt.Println(string(text))
}

This program will run successfully regardless of where the program is executed. This is useful in case the file could be called from multiple different locations, for instance, from a test directory.