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

huangapple go评论129阅读模式
英文:

extracting directory hierarchy using go language

问题

我正在尝试在Go语言中将文件夹的目录层次结构提取到数据结构中。filepath.Walk似乎是一个不错的选择,但到目前为止,我只能打印文件和文件夹的名称。这是我正在使用的代码:

  1. func main() {
  2. visit := func(path string, info os.FileInfo, err error) error {
  3. if info.IsDir() {
  4. fmt.Println("dir: ", path)
  5. } else {
  6. fmt.Println("file: ", path)
  7. }
  8. return nil
  9. }
  10. err := filepath.Walk("./", visit)
  11. if err != nil {
  12. log.Fatal(err)
  13. }
  14. }

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

  1. dir: folder1
  2. file: folder1/file1.txt
  3. file: folder1/file2.txt
  4. file: folder1/file3.txt
  5. file: folder1/file4.txt
  6. dir: folder1/folder2
  7. file: folder1/folder2/file5.txt
  8. file: folder1/folder2/file6.txt
  9. file: folder1/folder2/file7.txt
  10. file: folder1/folder2/file8.txt
  11. file: folder1/folder2/file9.txt

对于树形结构,我考虑使用类似以下的方式:

  1. type File struct {
  2. Name string
  3. Content string
  4. }
  5. type Folder struct {
  6. Name string
  7. Files []File
  8. Folders []Folder
  9. }

当然,欢迎任何建议。

我该如何将其转换为Go中的树形结构?是否有更简单的方法来实现这个?

英文:

I'm trying to extract the directory hierarchy of a folder into a datastructure in go language. filepath.Walk seems to be the way to go but all I can do so far is to print the names of files and folders. Here's what I'm using:

  1. func main() {
  2. visit := func(path string, info os.FileInfo, err error) error {
  3. if info.IsDir() {
  4. fmt.Println("dir: ", path)
  5. } else {
  6. fmt.Println("file: ", path)
  7. }
  8. return nil
  9. }
  10. err := filepath.Walk("./", visit)
  11. if err != nil {
  12. log.Fatal(err)
  13. }
  14. }

this prints the names of folders like:

  1. dir: folder1
  2. file: folder1/file1.txt
  3. file: folder1/file2.txt
  4. file: folder1/file3.txt
  5. file: folder1/file4.txt
  6. dir: folder1/folder2
  7. file: folder1/folder2/file5.txt
  8. file: folder1/folder2/file6.txt
  9. file: folder1/folder2/file7.txt
  10. file: folder1/folder2/file8.txt
  11. file: folder1/folder2/file9.txt

for tree structure I thought about using something like:

  1. type File struct {
  2. Name string
  3. Content string
  4. }
  5. type Folder struct {
  6. Name string
  7. Files []File
  8. Folders []Folder
  9. }

but of course any suggestions are welcomed.

How can I convert this to a tree structure in go? Is there an easier way to do this?

答案1

得分: 4

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

我知道这对于这个问题的原始作者来说来得太晚了,但我还是在这里发布它,以防有人正在寻找类似的东西。欢迎提交拉取请求 使用Go语言提取目录层次结构

英文:

I needed something similar for a little app of mine so I wrote a tiny separate library which is available for your viewing pleasure on Github. Since I needed built-in JSON serialization for the returned os.FileInfo, I added it as well.

I know it comes too late for the original author of this question but posting it here anyway in case someone is looking for something similar. Pull requests readily accepted 使用Go语言提取目录层次结构

答案2

得分: 3

根据Go标准库中没有现成的解决方案,树结构非常适合使用递归方法。我在File和Folder类型上定义了addFileaddFolder方法。从根文件夹开始,在Walk中可以调用这些方法。如果得到a/b/c,我们将调用root.addFile(a, b, c)a.addFile(b, c)b.addFile(c)

我还将Folder.Folders更改为map,因为filepath.Walk总是给我们提供完整路径,所以我们可以将其拆分并在文件夹映射中查找其组件。

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

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

  1. package main
  2. import (
  3. "log"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. )
  8. type File struct {
  9. Name string
  10. }
  11. type Folder struct {
  12. Name string
  13. Files []File
  14. Folders map[string]*Folder
  15. }
  16. func newFolder(name string) *Folder {
  17. return &Folder{name, []File{}, make(map[string]*Folder)}
  18. }
  19. func (f *Folder) getFolder(name string) *Folder {
  20. if nextF, ok := f.Folders[name]; ok {
  21. return nextF
  22. } else {
  23. log.Fatalf("Expected nested folder %v in %v\n", name, f.Name)
  24. }
  25. return &Folder{} // cannot happen
  26. }
  27. func (f *Folder) addFolder(path []string) {
  28. for i, segment := range path {
  29. if i == len(path)-1 { // last segment == new folder
  30. f.Folders[segment] = newFolder(segment)
  31. } else {
  32. f.getFolder(segment).addFolder(path[1:])
  33. }
  34. }
  35. }
  36. func (f *Folder) addFile(path []string) {
  37. for i, segment := range path {
  38. if i == len(path)-1 { // last segment == file
  39. f.Files = append(f.Files, File{segment})
  40. } else {
  41. f.getFolder(segment).addFile(path[1:])
  42. return
  43. }
  44. }
  45. }
  46. func (f *Folder) String() string {
  47. var str string
  48. for _, file := range f.Files {
  49. str += f.Name + string(filepath.Separator) + file.Name + "\n"
  50. }
  51. for _, folder := range f.Folders {
  52. str += folder.String()
  53. }
  54. return str
  55. }
  56. func main() {
  57. startPath := "."
  58. rootFolder := newFolder(startPath)
  59. visit := func(path string, info os.FileInfo, err error) error {
  60. segments := strings.Split(path, string(filepath.Separator))
  61. if info.IsDir() {
  62. if path != startPath {
  63. rootFolder.addFolder(segments)
  64. }
  65. } else {
  66. rootFolder.addFile(segments)
  67. }
  68. return nil
  69. }
  70. err := filepath.Walk(startPath, visit)
  71. if err != nil {
  72. log.Fatal(err)
  73. }
  74. log.Printf("%v\n", rootFolder)
  75. }
英文:

AFAIK there is nothing ready-made for this in the Go standard lib.

Tree structures lend themselves well to a recursive approach. I defined addFile and addFolder methods on your File and Folder types. Starting with a root folder, you can then call these methods in Walk. If you get a/b/c, we'll be calling root.addFile(a, b, c), a.addFile(b, c), b.addFile(c).

I also changed Folder.Folders to a map, because filepath.Walk always gives us full paths, so we can split those and look up their components in the folder map.

Here is some quick and dirty code that probably has bugs and doesn't do full error checking. It only works for the current directory, but that should be easy to fix.

I also added a String() method on Folder, which is recognized by the compiler and will be used when printing out instances of the type.

  1. package main
  2. import (
  3. "log"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. )
  8. type File struct {
  9. Name string
  10. }
  11. type Folder struct {
  12. Name string
  13. Files []File
  14. Folders map[string]*Folder
  15. }
  16. func newFolder(name string) *Folder {
  17. return &Folder{name, []File{}, make(map[string]*Folder)}
  18. }
  19. func (f *Folder) getFolder(name string) *Folder {
  20. if nextF, ok := f.Folders[name]; ok {
  21. return nextF
  22. } else {
  23. log.Fatalf("Expected nested folder %v in %v\n", name, f.Name)
  24. }
  25. return &Folder{} // cannot happen
  26. }
  27. func (f *Folder) addFolder(path []string) {
  28. for i, segment := range path {
  29. if i == len(path)-1 { // last segment == new folder
  30. f.Folders[segment] = newFolder(segment)
  31. } else {
  32. f.getFolder(segment).addFolder(path[1:])
  33. }
  34. }
  35. }
  36. func (f *Folder) addFile(path []string) {
  37. for i, segment := range path {
  38. if i == len(path)-1 { // last segment == file
  39. f.Files = append(f.Files, File{segment})
  40. } else {
  41. f.getFolder(segment).addFile(path[1:])
  42. return
  43. }
  44. }
  45. }
  46. func (f *Folder) String() string {
  47. var str string
  48. for _, file := range f.Files {
  49. str += f.Name + string(filepath.Separator) + file.Name + "\n"
  50. }
  51. for _, folder := range f.Folders {
  52. str += folder.String()
  53. }
  54. return str
  55. }
  56. func main() {
  57. startPath := "."
  58. rootFolder := newFolder(startPath)
  59. visit := func(path string, info os.FileInfo, err error) error {
  60. segments := strings.Split(path, string(filepath.Separator))
  61. if info.IsDir() {
  62. if path != startPath {
  63. rootFolder.addFolder(segments)
  64. }
  65. } else {
  66. rootFolder.addFile(segments)
  67. }
  68. return nil
  69. }
  70. err := filepath.Walk(startPath, visit)
  71. if err != nil {
  72. log.Fatal(err)
  73. }
  74. log.Printf("%v\n", rootFolder)
  75. }

答案3

得分: 2

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

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. )
  10. func main() {
  11. tree := BuildTree(os.Args[1])
  12. fmt.Println(tree)
  13. }
  14. type File struct {
  15. Name string
  16. }
  17. type Folder struct {
  18. Name string
  19. Files []*File
  20. Folders map[string]*Folder
  21. }
  22. func (f *Folder) String() string {
  23. j, _ := json.Marshal(f)
  24. return string(j)
  25. }
  26. func BuildTree(dir string) *Folder {
  27. dir = path.Clean(dir)
  28. var tree *Folder
  29. var nodes = map[string]interface{}{}
  30. var walkFun filepath.WalkFunc = func(p string, info os.FileInfo, err error) error {
  31. if info.IsDir() {
  32. nodes

    = &Folder{path.Base(p), []*File{}, map[string]*Folder{}}

  33. } else {

  34. nodes

    = &File{path.Base(p)}

  35. }

  36. return nil

  37. }

  38. err := filepath.Walk(dir, walkFun)

  39. if err != nil {

  40. log.Fatal(err)

  41. }

  42. for key, value := range nodes {

  43. var parentFolder *Folder

  44. if key == dir {

  45. tree = value.(*Folder)

  46. continue

  47. } else {

  48. parentFolder = nodes[path.Dir(key)].(*Folder)

  49. }

  50. switch v := value.(type) {

  51. case *File:

  52. parentFolder.Files = append(parentFolder.Files, v)

  53. case *Folder:

  54. parentFolder.Folders[v.Name] = v

  55. }

  56. }

  57. return tree

  58. }

英文:

only use one for loop and filepath.Walk

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. )
  10. func main() {
  11. tree := BuildTree(os.Args[1])
  12. fmt.Println(tree)
  13. }
  14. type File struct {
  15. Name string
  16. }
  17. type Folder struct {
  18. Name string
  19. Files []*File
  20. Folders map[string]*Folder
  21. }
  22. func (f *Folder) String() string {
  23. j, _ := json.Marshal(f)
  24. return string(j)
  25. }
  26. func BuildTree(dir string) *Folder {
  27. dir = path.Clean(dir)
  28. var tree *Folder
  29. var nodes = map[string]interface{}{}
  30. var walkFun filepath.WalkFunc = func(p string, info os.FileInfo, err error) error {
  31. if info.IsDir() {
  32. nodes

    = &Folder{path.Base(p), []*File{}, map[string]*Folder{}}

  33. } else {

  34. nodes

    = &File{path.Base(p)}

  35. }

  36. return nil

  37. }

  38. err := filepath.Walk(dir, walkFun)

  39. if err != nil {

  40. log.Fatal(err)

  41. }

  42. for key, value := range nodes {

  43. var parentFolder *Folder

  44. if key == dir {

  45. tree = value.(*Folder)

  46. continue

  47. } else {

  48. parentFolder = nodes[path.Dir(key)].(*Folder)

  49. }

  50. switch v := value.(type) {

  51. case *File:

  52. parentFolder.Files = append(parentFolder.Files, v)

  53. case *Folder:

  54. parentFolder.Folders[v.Name] = v

  55. }

  56. }

  57. return tree

  58. }

答案4

得分: 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",
})
}

  1. for _, v := range f.Files {
  2. result = append(result, map[string]interface{}{
  3. "id": v.Id,
  4. "name": v.Name,
  5. "type": "file",
  6. })
  7. }
  8. 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",
},
}

  1. breadcrumb := "/print/some2"
  2. startPath := "/"
  3. rootFolder := newFolder(startPath)
  4. for _, path := range arrayPaths {
  5. filePath := path.(IS)["filePath"]
  6. fileId := path.(IS)["id"]
  7. splitPath := DeleteEmptyElements(strings.Split(filePath, "/"))
  8. tmpFolder := rootFolder
  9. for _, item := range splitPath {
  10. if isFile(item) {
  11. tmpFolder.addFile(item, fileId)
  12. } else {
  13. if item != startPath {
  14. tmpFolder.addFolder(item)
  15. }
  16. tmpFolder = tmpFolder.getFolder(item)
  17. }
  18. }
  19. }
  20. currentFolder := rootFolder.getFolder("/")
  21. breadcrumbElements := DeleteEmptyElements(strings.Split(breadcrumb, "/"))
  22. for i, v := range breadcrumbElements {
  23. if currentFolder.existFolder(v) {
  24. currentFolder = currentFolder.getFolder(v)
  25. if i == len(breadcrumbElements)-1 {
  26. break
  27. }
  28. } else {
  29. currentFolder = currentFolder.getFolder(v)
  30. }
  31. }
  32. fmt.Println(currentFolder.getList())

}

英文:

Little modify

  1. package main
  2. import (
  3. "fmt"
  4. "path"
  5. "strings"
  6. )
  7. type File struct {
  8. Id string
  9. Name string
  10. }
  11. type Folder struct {
  12. Name string
  13. Files []File
  14. Folders map[string]*Folder
  15. }
  16. func newFolder(name string) *Folder {
  17. return &Folder{name, []File{}, make(map[string]*Folder)}
  18. }
  19. func (f *Folder) getFolder(name string) *Folder {
  20. if nextF, ok := f.Folders[name]; ok {
  21. return nextF
  22. } else if f.Name == name {
  23. return f
  24. } else {
  25. return &Folder{}
  26. }
  27. }
  28. func (f *Folder) existFolder(name string) bool {
  29. for _, v := range f.Folders {
  30. if v.Name == name {
  31. return true
  32. }
  33. }
  34. return false
  35. }
  36. func (f *Folder) addFolder(folderName string) {
  37. if !f.existFolder(folderName) {
  38. f.Folders[folderName] = newFolder(folderName)
  39. }
  40. }
  41. func (f *Folder) addFile(fileName string, fileId string) {
  42. f.Files = append(f.Files, File{fileId, fileName})
  43. }
  44. func (f *Folder) getList() (result []map[string]interface{}) {
  45. for _, v := range f.Folders {
  46. result = append(result, map[string]interface{}{
  47. "name": v.Name,
  48. "type": "folder",
  49. })
  50. }
  51. for _, v := range f.Files {
  52. result = append(result, map[string]interface{}{
  53. "id": v.Id,
  54. "name": v.Name,
  55. "type": "file",
  56. })
  57. }
  58. return
  59. }
  60. func isFile(str string) bool {
  61. if path.Ext(str) != "" {
  62. return true
  63. }
  64. return false
  65. }
  66. func DeleteEmptyElements(s []string) []string {
  67. var r []string
  68. for _, str := range s {
  69. if str != "" {
  70. r = append(r, str)
  71. }
  72. }
  73. return r
  74. }
  75. type IS map[string]string
  76. func main() {
  77. arrayPaths := []interface{}{
  78. IS{
  79. "id": "1",
  80. "filePath": "/print/some/com.png",
  81. },
  82. IS{
  83. "id": "2",
  84. "filePath": "/print/some2/com412412.png",
  85. },
  86. IS{
  87. "id": "3",
  88. "filePath": "/print/some2/41241241241.png",
  89. },
  90. }
  91. breadcrumb := "/print/some2"
  92. startPath := "/"
  93. rootFolder := newFolder(startPath)
  94. for _, path := range arrayPaths {
  95. filePath := path.(IS)["filePath"]
  96. fileId := path.(IS)["id"]
  97. splitPath := DeleteEmptyElements(strings.Split(filePath, "/"))
  98. tmpFolder := rootFolder
  99. for _, item := range splitPath {
  100. if isFile(item) {
  101. tmpFolder.addFile(item, fileId)
  102. } else {
  103. if item != startPath {
  104. tmpFolder.addFolder(item)
  105. }
  106. tmpFolder = tmpFolder.getFolder(item)
  107. }
  108. }
  109. }
  110. currentFolder := rootFolder.getFolder("/")
  111. breadcrumbElements := DeleteEmptyElements(strings.Split(breadcrumb, "/"))
  112. for i, v := range breadcrumbElements {
  113. if currentFolder.existFolder(v) {
  114. currentFolder = currentFolder.getFolder(v)
  115. if i == len(breadcrumbElements)-1 {
  116. break
  117. }
  118. } else {
  119. currentFolder = currentFolder.getFolder(v)
  120. }
  121. }
  122. fmt.Println(currentFolder.getList())
  123. }

答案5

得分: 0

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

  1. package main
  2. import (
  3. "io/fs"
  4. "os"
  5. "path/filepath"
  6. "testing/fstest"
  7. )
  8. func main() {
  9. m := make(fstest.MapFS)
  10. walk := func(s string, d fs.DirEntry, e error) error {
  11. if e != nil { return e }
  12. if ! d.IsDir() {
  13. data, e := os.ReadFile(s)
  14. if e != nil { return e }
  15. m
    展开收缩
    = &fstest.MapFile{Data: data}
  16. }
  17. return nil
  18. }
  19. filepath.WalkDir(`C:\go\src\net`, walk)
  20. data := m[`C:\go\src\net\textproto\writer.go`].Data[:44]
  21. println(string(data) == "// Copyright 2010 The Go Authors. All rights")
  22. }

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

英文:

Starting with Go 1.16, you can use fstest.MapFS as the datastructure you are
asking for:

  1. package main
  2. import (
  3. "io/fs"
  4. "os"
  5. "path/filepath"
  6. "testing/fstest"
  7. )
  8. func main() {
  9. m := make(fstest.MapFS)
  10. walk := func(s string, d fs.DirEntry, e error) error {
  11. if e != nil { return e }
  12. if ! d.IsDir() {
  13. data, e := os.ReadFile(s)
  14. if e != nil { return e }
  15. m
    展开收缩
    = &fstest.MapFile{Data: data}
  16. }
  17. return nil
  18. }
  19. filepath.WalkDir(`C:\go\src\net`, walk)
  20. data := m[`C:\go\src\net\textproto\writer.go`].Data[:44]
  21. println(string(data) == "// Copyright 2010 The Go Authors. All rights")
  22. }

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

huangapple
  • 本文由 发表于 2012年9月30日 07:21:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/12657365.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定