怎么使用Golang递归获取目录下所有文件

其他教程   发布日期:2023年07月12日   浏览次数:423

这篇文章主要讲解了“怎么使用Golang递归获取目录下所有文件”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用Golang递归获取目录下所有文件”吧!

1.问题

如果我想获取一个目录下的所有文件列表,使用 Golang 该如何实现呢?

比如有个目录 dir 结构如下:

tree dir
dir
├── bar.txt
├── foo.txt
└── subdir
└── baz.txt

那么如何获取 dir 目录下的所有文件路径呢?

dir/foo.txt
dir/bar.txt
dir/subdir/baz.txt

2.io/ioutil

标准库 io/ioutil 包提供了一个函数

  1. ReadDir()
可以获取指定目录下的所有内容,按文件名排序,返回
  1. []fs.FileInfo
切片来描述目录中的所有内容。
  1. func ReadDir(dirname string) ([]fs.FileInfo, error)

利用

  1. ioutil.ReadDir()
我们可以获取目录中的所有文件吗?
  1. // ListDir lists all the file or dir names in the specified directory.
  2. // Note that ListDir don't traverse recursively.
  3. func ListDir(dirname string) ([]string, error) {
  4. infos, err := ioutil.ReadDir(dirname)
  5. if err != nil {
  6. return nil, err
  7. }
  8. names := make([]string, len(infos))
  9. for i, info := range infos {
  10. names[i] = info.Name()
  11. }
  12. return names, nil
  13. }

我们来测试一下:

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main() {
  7. names, _ := ListDir("dir")
  8. fmt.Printf("names:%v
  9. ", names)
  10. }

运行输出:

names:[bar.txt foo.txt subdir]

可见 ioutil.ReadDir() 并不会递归获取子目录的内容。

3.递归获取

如果想递归获子目录的内容,该如何实现呢?

我们可以递归的调用我们自己的函数,来递归遍历子目录。

  1. // GetDirAllFilePaths gets all the file paths in the specified directory recursively.
  2. func GetDirAllFilePaths(dirname string) ([]string, error) {
  3. // Remove the trailing path separator if dirname has.
  4. dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))
  5. infos, err := ioutil.ReadDir(dirname)
  6. if err != nil {
  7. return nil, err
  8. }
  9. paths := make([]string, 0, len(infos))
  10. for _, info := range infos {
  11. path := dirname + string(os.PathSeparator) + info.Name()
  12. if info.IsDir() {
  13. tmp, err := GetDirAllFilePaths(path)
  14. if err != nil {
  15. return nil, err
  16. }
  17. paths = append(paths, tmp...)
  18. continue
  19. }
  20. paths = append(paths, path)
  21. }
  22. return paths, nil
  23. }

我们来测试一下:

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "strings"
  7. )
  8. func main() {
  9. paths, _ := GetDirAllFilePaths("dir/")
  10. for _, path := range paths {
  11. fmt.Println(path)
  12. }
  13. }

运行输出:

dir/bar.txt
dir/foo.txt
dir/subdir/baz.txt

哇,看起来大功告成。但果真如此吗?

4.包含符号链接的情况

如果我们此时在目录 dir 中加入一个符号链接,指向另外一个目录,那结果会如何呢?

tree dir
dir
├── bar.txt
├── foo.txt
├── subdir
│ └── baz.txt
└── zipln -> ../zip

tree zip
zip
└── qux.txt

还是运行调用 GetDirAllFilePaths(),我们得到的结果如下:

dir/bar.txt
dir/foo.txt
dir/subdir/baz.txt
dir/zipln

可见,当子目录为符号链接时,我们并没有访问链接指向的目标文件。

我们改变一下实现,当子目录是符号链接时,读取目标目录下的文件。

  1. // GetDirAllFilePathsFollowSymlink gets all the file paths in the specified directory recursively.
  2. func GetDirAllFilePathsFollowSymlink(dirname string) ([]string, error) {
  3. // Remove the trailing path separator if dirname has.
  4. dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))
  5. infos, err := ioutil.ReadDir(dirname)
  6. if err != nil {
  7. return nil, err
  8. }
  9. paths := make([]string, 0, len(infos))
  10. for _, info := range infos {
  11. path := dirname + string(os.PathSeparator) + info.Name()
  12. realInfo, err := os.Stat(path)
  13. if err != nil {
  14. return nil, err
  15. }
  16. if realInfo.IsDir() {
  17. tmp, err := GetDirAllFilePathFollowSymlink(path)
  18. if err != nil {
  19. return nil, err
  20. }
  21. paths = append(paths, tmp...)
  22. continue
  23. }
  24. paths = append(paths, path)
  25. }
  26. return paths, nil
  27. }

我们来测试一下:

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "strings"
  7. )
  8. func main() {
  9. paths, _ := GetDirAllFilePathsFollowSymlink("dir/")
  10. for _, path := range paths {
  11. fmt.Println(path)
  12. }
  13. }

运行输出:

dir/bar.txt
dir/foo.txt
dir/subdir/baz.txt
dir/zipln/qux.txt

perfect,这就是我们想要的效果。

5.同时返回目录的路径

有时,我们还需要目录路径,即获取指定目录下的文件和子目录的路径。比如在对一个目录进行压缩时会需要。

还是以上文 dir 目录为例,我们想要的结果是:

dir
dir/bar.txt
dir/foo.txt
dir/subdir
dir/subdir/baz.txt
dir/zipln
dir/zipln/qux.txt

我们只要稍微改造 GetDirAllFilePaths 和 GetDirAllFilePathsFollowSymlink 即可,在遍历时把当前的目录加入结果集。

并更名 GetDirAllFilePaths 为 GetDirAllEntryPaths,GetDirAllFilePathsFollowSymlink 为 GetDirAllEntryPathsFollowSymlink,因为条目(Entry)比文件(File)语义更符合函数的功能,因为不仅可以获取文件,也可以获取目录的路径。

  1. // GetDirAllEntryPaths gets all the file or dir paths in the specified directory recursively.
  2. // Note that GetDirAllEntryPaths won't follow symlink if the subdir is a symbolic link.
  3. func GetDirAllEntryPaths(dirname string, incl bool) ([]string, error) {
  4. // Remove the trailing path separator if dirname has.
  5. dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))
  6. infos, err := ioutil.ReadDir(dirname)
  7. if err != nil {
  8. return nil, err
  9. }
  10. paths := make([]string, 0, len(infos))
  11. // Include current dir.
  12. if incl {
  13. paths = append(paths, dirname)
  14. }
  15. for _, info := range infos {
  16. path := dirname + string(os.PathSeparator) + info.Name()
  17. if info.IsDir() {
  18. tmp, err := GetDirAllEntryPaths(path, incl)
  19. if err != nil {
  20. return nil, err
  21. }
  22. paths = append(paths, tmp...)
  23. continue
  24. }
  25. paths = append(paths, path)
  26. }
  27. return paths, nil
  28. }
  29. // GetDirAllEntryPathsFollowSymlink gets all the file or dir paths in the specified directory recursively.
  30. func GetDirAllEntryPathsFollowSymlink(dirname string, incl bool) ([]string, error) {
  31. // Remove the trailing path separator if dirname has.
  32. dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))
  33. infos, err := ioutil.ReadDir(dirname)
  34. if err != nil {
  35. return nil, err
  36. }
  37. paths := make([]string, 0, len(infos))
  38. // Include current dir.
  39. if incl {
  40. paths = append(paths, dirname)
  41. }
  42. for _, info := range infos {
  43. path := dirname + string(os.PathSeparator) + info.Name()
  44. realInfo, err := os.Stat(path)
  45. if err != nil {
  46. return nil, err
  47. }
  48. if realInfo.IsDir() {
  49. tmp, err := GetDirAllEntryPathsFollowSymlink(path, incl)
  50. if err != nil {
  51. return nil, err
  52. }
  53. paths = append(paths, tmp...)
  54. continue
  55. }
  56. paths = append(paths, path)
  57. }
  58. return paths, nil
  59. }

我们测试一下。

  1. func main() {
  2. fmt.Println("not follow symlink:")
  3. paths, _ := GetDirAllEntryPaths("dir/", true)
  4. for _, path := range paths {
  5. fmt.Println(path)
  6. }
  7. fmt.Println("
  8. follow symlink:")
  9. paths, _ = GetDirAllEntryPathsFollowSymlink("dir/", true)
  10. for _, path := range paths {
  11. fmt.Println(path)
  12. }
  13. }

运行输出:

not follow symlink:
dir
dir/bar.txt
dir/foo.txt
dir/subdir
dir/subdir/baz.txt
dir/zipln

follow symlink:
dir
dir/bar.txt
dir/foo.txt
dir/subdir
dir/subdir/baz.txt
dir/zipln
dir/zipln/qux.txt

6.go-huge-util

以上函数已放置开源库 go-huge-util,可 import 直接使用。

  1. package main
  2. import (
  3. "github.com/dablelv/go-huge-util/file"
  4. )
  5. func main() {
  6. // 获取目录下所有文件和子目录名称(不会递归)。
  7. names, _ := file.ListDir("dir")
  8. // 递归获取目录下所有文件路径(不解析符号链接)
  9. paths, _ := file.GetDirAllEntryPaths("dir", false)
  10. // 递归获取目录下所有文件和目录路径(不解析符号链接)
  11. paths, _ = file.GetDirAllEntryPaths("dir", true)
  12. // 递归获取目录下所有文件路径(解析符号链接)
  13. paths, _ = file.GetDirAllEntryPathsFollowSymlink("dir", false)
  14. // 递归获取目录下所有文件与目录路径(解析符号链接)
  15. paths, _ = file.GetDirAllEntryPathsFollowSymlink("dir/", true)
  16. }

以上就是怎么使用Golang递归获取目录下所有文件的详细内容,更多关于怎么使用Golang递归获取目录下所有文件的资料请关注九品源码其它相关文章!