【问题标题】:How can I open files relative to my GOPATH?如何打开与我的 GOPATH 相关的文件?
【发布时间】:2013-06-08 21:46:06
【问题描述】:

我正在使用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

然后我得到这个错误:

恐慌:打开数据/file.txt:没有这样的文件或目录

如何使用它们的相对路径打开文件,尤其是当它们与我的 Go 代码一起存在时?

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

【问题讨论】:

  • 程序编译后 GOPATH 没有太大意义,分发时更没有意义。
  • 您似乎想要的更像是在已编译程序中嵌入文件。
  • 有点...除了我希望数据文件与源文件分开。数据文件对程序的功能至关重要。因此,当有人拉下我的源代码(连同它旁边的数据文件)并编译并运行它时,数据文件会使用相对路径加载,因为它们存在于源代码附近或程序执行的位置附近。跨度>
  • 编译后的二进制文件应该不依赖于源文件的位置,但是如果有一种方法可以创建一个包含外部资源副本的可执行包依赖。
  • 这是一个关于捆绑资源的相关问题,可能就足够了,尽管这不是我的首选方法:stackoverflow.com/questions/13904441/…——或者这个:stackoverflow.com/q/9443418/1048862

标签: go filepath


【解决方案1】:

这似乎工作得很好:

import "os"
import "io/ioutil"

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

【讨论】:

  • 这是不好的形式,因为 / 是特定于操作系统的,最好在连接路径时使用 filepath.join() 因为它将使用 os.PathSeperator
  • 让我为那些愿意遵循上一条评论中的(好)建议的人节省时间:使用import "path/filepath" 并致电filepath.Join(...)(大写J)。 :-)
  • os.Open(pwd + "../mocks/product/default.json") 这个案子你是怎么处理的?它把它当作文字。
  • os.Open 对我来说很好:csvfile, err := os.Open("/mnt/c/Users/dmotta/Downloads/archivos_csv/tb_desp_new_201904081633.csv")
【解决方案2】:

我认为 Alec Thomas 提供了答案,但根据我的经验,它并非万无一失。我在将资源编译到二进制文件时遇到的一个问题是,编译可能需要大量内存,具体取决于资产的大小。如果它们很小,那么可能没什么好担心的。在我的特定场景中,一个 1MB 的字体文件导致编译需要大约 1GB 的内存来编译。这是一个问题,因为我希望它可以在 Raspberry Pi 上获取。这是 Go 1.0; Go 1.1 中的情况可能有所改进。

因此,在这种特殊情况下,我选择仅使用 go/build 包根据导入路径查找程序的 source directory。当然,这要求您的目标设置了GOPATH 并且源可用。所以它并不是在所有情况下都是理想的解决方案。

【讨论】:

    【解决方案3】:

    我写了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。

    【讨论】:

    • @BurntSushi5 有好处;我的数据文件大小为数百 MB。这很酷,但我不认为将纯文本文件编译成二进制文件是一个可行的选择。
    【解决方案4】:

    嗯...path/filepath 包有 Abs() 可以满足我的需要(到目前为止),虽然有点不方便:

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

    然后我使用absPath 加载文件,它工作正常。

    请注意,在我的情况下,数据文件位于与运行程序的main 包不同的包中。如果它们都在同一个包中,我会删除领先的../mypackage/。由于这条路径显然是相对的,因此不同的程序会有不同的结构,需要进行相应的调整。

    如果有更好的方法通过 Go 程序使用外部资源并保持其可移植性,请随时提供另一个答案。

    【讨论】:

    • 哈,差不多一年后进入 Go,从那以后我再也没有遇到过通过相对路径打开文件的问题,也不必使用这个小技巧。我想这意味着我学到了一些东西。
    • 你学到了什么? :) 我们不应该跨包共享这样的文件吗?
    • @Matt,分享爱。如果您对我们有更好的建议,请不要使用这个“小技巧”。你是谷歌搜索结果的第一名。
    • 这个问题来自我在 Go 编程的第一周。更好的问题是如何将所需的数据与程序捆绑在一起。我在这里的回答是针对我非常具体的情况,我希望人们不要随意使用它。
    • 我读了这篇文章,并认为我必须扩展 ioutil.ReadFile 的相对路径才能正常工作,但这完全没有必要。这很好用:ioutil.ReadFile("../mypackage/data/file.txt")。可能还有许多其他情况需要构建绝对路径,但似乎与 io.ReadFile 一起使用不是其中之一。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-15
    • 2019-06-19
    • 2013-04-25
    相关资源
    最近更新 更多