英文:
How can I open files relative to my 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
但是我得到了这个错误:
panic: open data/file.txt: no such file or directory
如何使用相对路径打开文件,特别是如果它们与我的Go代码放在一起?
(**请注意,我的问题特别是关于相对于GOPATH打开文件。**在Go中使用任何相对路径打开文件就像给出相对路径而不是绝对路径一样简单;文件是相对于编译二进制文件的工作目录打开的。在我的情况下,我想要相对于编译二进制文件的位置打开文件。事后看来,这是一个糟糕的设计决策。)
英文:
I'm using io/ioutil
to read a small text file:
fileBytes, err := ioutil.ReadFile("/absolute/path/to/file.txt")
And that works fine, but this isn't exactly portable. In my case, the files I want to open are in my GOPATH, for example:
/Users/matt/Dev/go/src/github.com/mholt/mypackage/data/file.txt
Since the data
folder rides right alongside the source code, I'd love to just specify the relative path:
data/file.txt
But then I get this error:
> panic: open data/file.txt: no such file or directory
How can I open files using their relative path, especially if they live alongside my Go code?
(Note that my question is specifically about opening files relative to the GOPATH. Opening files using any relative path in Go is as easy as giving the relative path instead of an absolute path; files are opened relative to the compiled binary's working directory. In my case, I want to open files relative to where the binary was compiled. In hindsight, this is a bad design decision.)
答案1
得分: 103
Hmm... path/filepath
包有一个Abs()
函数,可以满足我的需求(到目前为止),尽管有点不方便:
absPath, _ := filepath.Abs("../mypackage/data/file.txt")
然后我使用absPath
来加载文件,它可以正常工作。
请注意,在我的情况下,数据文件位于与我运行程序的main
包不同的包中。如果它们都在同一个包中,我会删除前面的../mypackage/
。由于这个路径显然是相对路径,不同的程序将具有不同的结构,需要相应地进行调整。
如果有更好的方法来使用Go程序的外部资源并保持可移植性,请随时提供另一个答案。
英文:
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.
答案2
得分: 60
这似乎运行得相当好:
import "os"
import "io/ioutil"
pwd, _ := os.Getwd()
txt, _ := ioutil.ReadFile(pwd+"/path/to/file.txt")
英文:
this seems to work pretty well:
import "os"
import "io/ioutil"
pwd, _ := os.Getwd()
txt, _ := ioutil.ReadFile(pwd+"/path/to/file.txt")
答案3
得分: 13
我写了gobundle来解决这个问题。它可以从数据文件生成Go源代码,然后将其编译到二进制文件中。然后,您可以通过类似VFS的层访问文件数据。它完全可移植,支持添加整个文件树、压缩等功能。
缺点是您需要一个中间步骤来从源数据构建Go文件。我通常使用make来完成这个步骤。
以下是如何遍历包中的所有文件并读取字节的示例代码:
for _, name := range bundle.Files() {
r, _ := bundle.Open(name)
b, _ := ioutil.ReadAll(r)
fmt.Printf("file %s has length %d\n", name, len(b))
}
您可以在我的GeoIP包中看到它的实际使用示例。Makefile
生成代码,而geoip.go
使用了VFS。
英文:
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.
答案4
得分: 6
从Go 1.16开始,您可以使用embed包。这允许您将文件嵌入到运行的Go程序中。
给定文件结构:
-- main.go
-- data
\- file.txt
您可以使用go指令引用该文件
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))
}
无论程序在何处执行,此程序都将成功运行。这在文件可能从多个不同位置调用的情况下非常有用,例如从测试目录中调用。
英文:
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.
答案5
得分: 4
我认为Alec Thomas已经提供了答案,但根据我的经验,这并不是百分之百可靠的。我在将资源编译到二进制文件中遇到的一个问题是,根据你的资源大小,编译可能需要大量的内存。如果资源很小,那可能没什么可担心的。在我的特定情况下,一个1MB的字体文件导致编译需要大约1GB的内存。这是一个问题,因为我希望它在树莓派上能够正常运行。这是在Go 1.0版本下的情况,可能在Go 1.1版本中有所改进。
所以在这种特定情况下,我选择使用go/build
包根据导入路径找到程序的源目录。当然,这要求你的目标有设置好的GOPATH
并且源代码是可用的。所以这并不是所有情况下的理想解决方案。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论