使用新的 io.FS 到 fs。WalkDir & 列出跨文件系统类型的文件

Using the new io.FS to fs.WalkDir & list files across filesystem types

提问人:Alicia Wentworth 提问时间:11/6/2023 最后编辑:Alicia Wentworth 更新时间:11/6/2023 访问量:63

问:

我使用的是那种新的io。FS 抽象遍历文件系统并读取与我们内部拥有的文件扩展名匹配的每个文件的前 128 个字节。

这些文件位于本地文件系统和存档文件等(ZIP 和 Tar I thunk)中。

我正在使用 fs。WalkDir,传入 fs。FS (os.DIR 和 fstest。MapFS 在我的测试中)。走路时,我返回一个“文件”数组(实际上它们是 *.pzix 和 *.pzi 文件,这是我们的专有格式)。我找不到使用 FS 接口的合适方法来获取有关我正在处理的文件的一些信息。

我想:

  • 获取文件名
  • 获取文件大小
  • 获取 openfile 方法

我发现来自 Java/C# 的 Go 中的接口有点令人困惑。我希望对抽象进行操作,但我无法弄清楚如何获得文件本身的其他实现(例如。文件接口有 Stat() 和 read)。

我发现的最简单的事情是将路径和文件名存储在数组中,然后在我遍历数组时,确定它是否是操作系统。Dir 或 fstest。MapFS,但这似乎有悖常理:

func collectFiles(f fs.FS, root string) []string {
 var files []string
 fs.WalkDir(f, ".", func(p string, d fs.DirEntry, err error) error {
  if !d.IsDir() { // we also check a few other things in the filename here
   f = filepath.Abs(path.Join(root, p))
   files = append(files, f)
  }
 }
 return files
}

这给了我:

root = "m://" // mapfs
files = { "m://id-198271.pzi", "m://id-7125-092581.pzix"}

有没有更聪明的方法来处理抽象而不做这些事情?因为在返回数组后,我必须“打开”文件,读取前 128 个字节(签名)并对文件的其余部分进行哈希处理以确保其“有效”。

编辑:澄清一下,该方法是创建我们的主要命中文件列表,以便在另一种方法中处理。我希望将本地系统文件、zip 文件和 tar 文件传递到该方法中,以便它可以遍历存档中的文件并将它们添加到数组中。collectFiles

希望有一个 File 接口,我可以存储在数组中而不是字符串中,以便后续调用者可以在不知道底层是什么的情况下执行 f.open()。

IO

评论

1赞 Burak Serdar 11/6/2023
FS支持,这样就可以将文件,然后检查返回的文件是否实现。如果是这样,您可以读取 128 个字节,然后关闭它。根据您的描述,文件应该实现,因此您可以执行然后调用Openf.Openio.Readerio.Readerreader := file.(io.Reader)reader.Read(...)
0赞 Alicia Wentworth 11/6/2023
这很聪明!我不知道那:)谢谢。我最大的抱怨是,我们将步行和阅读分开,所以步行只会让我们看到一个巨大的文件列表,我们需要浏览 - 这个列表是本地文件的混合体,也是 TAR/ZIP 中的文件。你建议我在该数组中返回什么,这样我就不需要 fs 的后续用户了。FS 了解底层实现?
0赞 Burak Serdar 11/6/2023
您无法打开大量文件。因此,如果您不打算在行走时处理它们,则可以返回一个切片,其中每个元素都是一个包含对文件名的引用的结构。有了这两个,您可以一个接一个地打开和读取文件。fs
0赞 Alicia Wentworth 11/6/2023
谢谢你俩得出了相同的结论!

答:

1赞 Narayan Aritra 11/6/2023 #1

获取文件名

p是文件系统中的名称。

获取文件大小

通过调用获取尺寸fs.Stat(f, p)

获取 openfile 方法

打开文件f.Open(p)

例:

f := os.DirFS("/etc")
fs.WalkDir(f, ".", func(p string, d fs.DirEntry, err error) error {
    if !d.IsDir() {
        st, _ := fs.Stat(f, p)
        r, _ := f.Open(p)
        defer r.Close()

        // Read prefix
        var buf [md5.Size]byte
        n, _ := io.ReadFull(r, buf[:])

        // Hash remainder
        h := md5.New()
        _, _ = io.Copy(h, r)
        s := h.Sum(nil)

        fmt.Printf("%s %d %x %x\n", p, st.Size(), buf[:n], s)
    }
    return nil
})

为简洁起见,该示例忽略了错误。不要在实际代码中这样做。

https://go.dev/play/p/W7He_YNSZFU

评论

0赞 Alicia Wentworth 11/6/2023
谢谢,太好了!我应该澄清一下,p 确实给了我文件名(在当前文件系统中)。但正如我上面提到的,我正在创建一个文件列表,以解压缩和计算具有不同来源的财务数据。所以有些是os。DirFS,有些是 Zip,有些是 Tar 文件。我希望步行和后续读者(在 WalkDir 功能之外)不知道 ZIP/TAR,只是阅读......
0赞 Narayan Aritra 11/6/2023
@AliciaWentworth 在 walk 函数内执行工作(如图所示),或者创建 slice 并使用 name 和 fsys do 在 walk 函数之外执行工作。go.dev/play/p/LSNttserRA3type struct NameFS { name string; fsys fs.FS }
0赞 Alicia Wentworth 11/6/2023
谢谢,我在另一个项目中看到了这一点,并认为这可能是门票。谢谢。