8

我正在尝试将文件夹的目录层次结构提取到 go 语言的数据结构中。filepath.Walk似乎是要走的路,但到目前为止我所能做的就是打印文件和文件夹的名称。这是我正在使用的:

func main() {
    visit := func(path string, info os.FileInfo, err error) error {
        if info.IsDir() {
            fmt.Println("dir:  ", path)
        } else {
            fmt.Println("file: ", path)
        }
        return nil
    }

    err := filepath.Walk("./", visit)
    if err != nil {
        log.Fatal(err)
    }
}

这会打印文件夹的名称,例如:

dir:   folder1
file:  folder1/file1.txt
file:  folder1/file2.txt
file:  folder1/file3.txt
file:  folder1/file4.txt
dir:   folder1/folder2
file:  folder1/folder2/file5.txt
file:  folder1/folder2/file6.txt
file:  folder1/folder2/file7.txt
file:  folder1/folder2/file8.txt
file:  folder1/folder2/file9.txt

对于树结构,我考虑过使用类似的东西:

type File struct {
    Name string
    Content string
}

type Folder struct {
    Name    string
    Files   []File
    Folders []Folder
}

但当然欢迎任何建议。

如何在 go 中将其转换为树结构?有没有更简单的方法来做到这一点?

4

5 回答 5

4

我的一个小应用程序需要类似的东西,所以我编写了一个单独的小型库,供您在 Github 上查看。因为我需要为返回的 os.FileInfo 内置 JSON 序列化,所以我也添加了它。

我知道对于这个问题的原作者来说为时已晚,但无论如何都将它发布在这里,以防有人正在寻找类似的东西。拉取请求很容易被接受:)

于 2013-08-09T10:48:48.853 回答
3

AFAIK 在 Go 标准库中没有任何现成的。

树结构很适合递归方法。我在您的文件和文件夹类型上定义了方法addFileaddFolder从根文件夹开始,您可以在 Walk 中调用这些方法。如果你得到 a/b/c,我们会打电话给root.addFile(a, b, c), a.addFile(b, c), b.addFile(c)

我还将 Folder.Folders 更改为地图,因为 filepath.Walk 始终为我们提供完整路径,因此我们可以拆分它们并在文件夹地图中查找它们的组件。

这是一些快速而肮脏的代码,可能有错误并且没有进行完整的错误检查。它仅适用于当前目录,但这应该很容易修复。

我还在 Folder 上添加了一个 String() 方法,该方法被编译器识别,并将在打印出该类型的实例时使用。

package main

import (
    "log"
    "os"
    "path/filepath"
    "strings"
)

type File struct {
    Name string
}

type Folder struct {
    Name    string
    Files   []File
    Folders map[string]*Folder
}

func newFolder(name string) *Folder {
    return &Folder{name, []File{}, make(map[string]*Folder)}
}

func (f *Folder) getFolder(name string) *Folder {
    if nextF, ok := f.Folders[name]; ok {
        return nextF
    } else {
        log.Fatalf("Expected nested folder %v in %v\n", name, f.Name)
    }
    return &Folder{} // cannot happen
}

func (f *Folder) addFolder(path []string) {
    for i, segment := range path {
        if i == len(path)-1 { // last segment == new folder
            f.Folders[segment] = newFolder(segment)
        } else {
            f.getFolder(segment).addFolder(path[1:])
        }
    }
}

func (f *Folder) addFile(path []string) {
    for i, segment := range path {
        if i == len(path)-1 { // last segment == file
            f.Files = append(f.Files, File{segment})
        } else {
            f.getFolder(segment).addFile(path[1:])
            return
        }
    }
}

func (f *Folder) String() string {
    var str string
    for _, file := range f.Files {
        str += f.Name + string(filepath.Separator) + file.Name + "\n"
    }
    for _, folder := range f.Folders {
        str += folder.String()
    }
    return str
}

func main() {
    startPath := "."
    rootFolder := newFolder(startPath)

    visit := func(path string, info os.FileInfo, err error) error {
        segments := strings.Split(path, string(filepath.Separator))
        if info.IsDir() {
            if path != startPath {
                rootFolder.addFolder(segments)
            }
        } else {
            rootFolder.addFile(segments)
        }
        return nil
    }

    err := filepath.Walk(startPath, visit)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("%v\n", rootFolder)
}
于 2012-09-30T07:34:49.840 回答
0

小修改

package main

import (
    "fmt"
    "path"
    "strings"
)

type File struct {
    Id   string
    Name string
}

type Folder struct {
    Name    string
    Files   []File
    Folders map[string]*Folder
}

func newFolder(name string) *Folder {
    return &Folder{name, []File{}, make(map[string]*Folder)}
}

func (f *Folder) getFolder(name string) *Folder {
    if nextF, ok := f.Folders[name]; ok {
        return nextF
    } else if f.Name == name {
        return f
    } else {
        return &Folder{}
    }
}

func (f *Folder) existFolder(name string) bool {
    for _, v := range f.Folders {
        if v.Name == name {
            return true
        }
    }
    return false
}

func (f *Folder) addFolder(folderName string) {
    if !f.existFolder(folderName) {
        f.Folders[folderName] = newFolder(folderName)
    }
}

func (f *Folder) addFile(fileName string, fileId string) {
    f.Files = append(f.Files, File{fileId, fileName})
}

func (f *Folder) getList() (result []map[string]interface{}) {
    for _, v := range f.Folders {
        result = append(result, map[string]interface{}{
            "name": v.Name,
            "type": "folder",
        })
    }

    for _, v := range f.Files {
        result = append(result, map[string]interface{}{
            "id":   v.Id,
            "name": v.Name,
            "type": "file",
        })
    }
    return
}

func isFile(str string) bool {
    if path.Ext(str) != "" {
        return true
    }
    return false
}

func DeleteEmptyElements(s []string) []string {
    var r []string
    for _, str := range s {
        if str != "" {
            r = append(r, str)
        }
    }
    return r
}

type IS map[string]string

func main() {
    arrayPaths := []interface{}{
        IS{
            "id":       "1",
            "filePath": "/print/some/com.png",
        },
        IS{
            "id":       "2",
            "filePath": "/print/some2/com412412.png",
        },
        IS{
            "id":       "3",
            "filePath": "/print/some2/41241241241.png",
        },
    }

    breadcrumb := "/print/some2"

    startPath := "/"
    rootFolder := newFolder(startPath)

    for _, path := range arrayPaths {
        filePath := path.(IS)["filePath"]
        fileId := path.(IS)["id"]
        splitPath := DeleteEmptyElements(strings.Split(filePath, "/"))
        tmpFolder := rootFolder
        for _, item := range splitPath {
            if isFile(item) {
                tmpFolder.addFile(item, fileId)
            } else {
                if item != startPath {
                    tmpFolder.addFolder(item)
                }
                tmpFolder = tmpFolder.getFolder(item)
            }
        }
    }

    currentFolder := rootFolder.getFolder("/")
    breadcrumbElements := DeleteEmptyElements(strings.Split(breadcrumb, "/"))
    for i, v := range breadcrumbElements {
        if currentFolder.existFolder(v) {
            currentFolder = currentFolder.getFolder(v)
            if i == len(breadcrumbElements)-1 {
                break
            }
        } else {
            currentFolder = currentFolder.getFolder(v)
        }
    }

    fmt.Println(currentFolder.getList())
}
于 2017-11-10T14:37:15.347 回答
0

只使用一个 for 循环和 filepath.Walk

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    "path"
    "path/filepath"
)

func main() {
    tree := BuildTree(os.Args[1])
    fmt.Println(tree)
}

type File struct {
    Name string
}

type Folder struct {
    Name    string
    Files   []*File
    Folders map[string]*Folder
}

func (f *Folder) String() string {
    j, _ := json.Marshal(f)
    return string(j)
}

func BuildTree(dir string) *Folder {
    dir = path.Clean(dir)
    var tree *Folder
    var nodes = map[string]interface{}{}
    var walkFun filepath.WalkFunc = func(p string, info os.FileInfo, err error) error {
        if info.IsDir() {
            nodes[p] = &Folder{path.Base(p), []*File{}, map[string]*Folder{}}
        } else {
            nodes[p] = &File{path.Base(p)}
        }
        return nil
    }
    err := filepath.Walk(dir, walkFun)
    if err != nil {
        log.Fatal(err)
    }

    for key, value := range nodes {
        var parentFolder *Folder
        if key == dir {
            tree = value.(*Folder)
            continue
        } else {
            parentFolder = nodes[path.Dir(key)].(*Folder)
        }

        switch v := value.(type) {
        case *File:
            parentFolder.Files = append(parentFolder.Files, v)
        case *Folder:
            parentFolder.Folders[v.Name] = v
        }
    }

    return tree
}
于 2019-06-12T14:54:52.463 回答
0

从 Go 1.16 开始,您可以将fstest.MapFS其用作您要求的数据结构:

package main

import (
   "io/fs"
   "os"
   "path/filepath"
   "testing/fstest"
)

func main() {
   m := make(fstest.MapFS)
   walk := func(s string, d fs.DirEntry, e error) error {
      if e != nil { return e }
      if ! d.IsDir() {
         data, e := os.ReadFile(s)
         if e != nil { return e }
         m[s] = &fstest.MapFile{Data: data}
      }
      return nil
   }
   filepath.WalkDir(`C:\go\src\net`, walk)
   data := m[`C:\go\src\net\textproto\writer.go`].Data[:44]
   println(string(data) == "// Copyright 2010 The Go Authors. All rights")
}

https://golang.org/pkg/testing/fstest#MapFS

于 2021-04-22T20:33:51.707 回答