使用go语言提取目录层次结构

时间:2012-09-29 23:21:33

标签: tree go

我正在尝试使用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中的树结构?有更简单的方法吗?

5 个答案:

答案 0 :(得分:3)

AFAIK在Go标准库中没有为此做好准备。

树结构非常适合递归方法。我在您的文件和文件夹类型上定义了addFileaddFolder方法。从根文件夹开始,然后可以在Walk中调用这些方法。如果您获得了/ 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)
}

答案 1 :(得分:3)

我需要类似于我的一个小应用程序的东西,所以我写了一个很小的独立库,可以让你的观看愉快on Github。因为我需要为返回的os.FileInfo内置JSON序列化,所以我也添加了它。

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

答案 2 :(得分: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())
}

答案 3 :(得分:0)

仅将其中一个用于循环和文件路径。步行

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
}

答案 4 :(得分: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