【问题标题】:extracting directory hierarchy using go language使用go语言提取目录层次结构
【发布时间】:2021-04-23 04:33:51
【问题描述】:

我正在尝试将文件夹的目录层次结构提取到 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 中将其转换为树结构?有没有更简单的方法来做到这一点?

【问题讨论】:

    标签: tree go


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      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/30 13:25:23 Expected nested folder pages in pages exit status 1
      • 不确定是什么原因造成的。正如我所说,这是说明概念的快速而肮脏的代码。您应该能够从那里调试和/或更改它。
      • 由于代码比我预期的要长,我决定在我的设计中采用另一种方式。同时,我会将问题留待一段时间,以防有人提出优雅的解决方案。感谢您的帮助..
      【解决方案3】:

      只使用一个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
      }
      

      【讨论】:

        【解决方案4】:

        小修改

        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())
        }
        

        【讨论】:

        • 请解释您编辑的内容以及为什么现在可以使用。
        【解决方案5】:

        从 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

        【讨论】:

          猜你喜欢
          • 2010-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-05-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多