Golang递归获取目录下所有文件方法实例

 更新时间:2023年02月27日 09:17:44   作者:恋喵大鲤鱼  
这篇文章主要给大家介绍了关于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 包提供了一个函数 ReadDir() 可以获取指定目录下的所有内容,按文件名排序,返回 []fs.FileInfo 切片来描述目录中的所有内容。

func ReadDir(dirname string) ([]fs.FileInfo, error)

利用 ioutil.ReadDir() 我们可以获取目录中的所有文件吗?

// ListDir lists all the file or dir names in the specified directory.
// Note that ListDir don't traverse recursively.
func ListDir(dirname string) ([]string, error) {
	infos, err := ioutil.ReadDir(dirname)
	if err != nil {
		return nil, err
	}
	names := make([]string, len(infos))
	for i, info := range infos {
		names[i] = info.Name()
	}
	return names, nil
}

我们来测试一下:

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	names, _ := ListDir("dir")
	fmt.Printf("names:%v\n", names)
}

运行输出:

names:[bar.txt foo.txt subdir]

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

3.递归获取

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

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

// GetDirAllFilePaths gets all the file paths in the specified directory recursively.
func GetDirAllFilePaths(dirname string) ([]string, error) {
	// Remove the trailing path separator if dirname has.
	dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))

	infos, err := ioutil.ReadDir(dirname)
	if err != nil {
		return nil, err
	}

	paths := make([]string, 0, len(infos))
	for _, info := range infos {
		path := dirname + string(os.PathSeparator) + info.Name()
		if info.IsDir() {
			tmp, err := GetDirAllFilePaths(path)
			if err != nil {
				return nil, err
			}
			paths = append(paths, tmp...)
			continue
		}
		paths = append(paths, path)
	}
	return paths, nil
}

我们来测试一下:

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

func main() {
	paths, _ := GetDirAllFilePaths("dir/")
	for _, path := range paths {
		fmt.Println(path)
	}
}

运行输出:

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

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

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

// GetDirAllFilePathsFollowSymlink gets all the file paths in the specified directory recursively.
func GetDirAllFilePathsFollowSymlink(dirname string) ([]string, error) {
	// Remove the trailing path separator if dirname has.
	dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))

	infos, err := ioutil.ReadDir(dirname)
	if err != nil {
		return nil, err
	}

	paths := make([]string, 0, len(infos))
	for _, info := range infos {
		path := dirname + string(os.PathSeparator) + info.Name()
		realInfo, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if realInfo.IsDir() {
			tmp, err := GetDirAllFilePathFollowSymlink(path)
			if err != nil {
				return nil, err
			}
			paths = append(paths, tmp...)
			continue
		}
		paths = append(paths, path)
	}
	return paths, nil
}

我们来测试一下:

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

func main() {
	paths, _ := GetDirAllFilePathsFollowSymlink("dir/")
	for _, path := range paths {
		fmt.Println(path)
	}
}

运行输出:

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)语义更符合函数的功能,因为不仅可以获取文件,也可以获取目录的路径。

// GetDirAllEntryPaths gets all the file or dir paths in the specified directory recursively.
// Note that GetDirAllEntryPaths won't follow symlink if the subdir is a symbolic link.
func GetDirAllEntryPaths(dirname string, incl bool) ([]string, error) {
	// Remove the trailing path separator if dirname has.
	dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))

	infos, err := ioutil.ReadDir(dirname)
	if err != nil {
		return nil, err
	}

	paths := make([]string, 0, len(infos))
	// Include current dir.
	if incl {
		paths = append(paths, dirname)
	}

	for _, info := range infos {
		path := dirname + string(os.PathSeparator) + info.Name()
		if info.IsDir() {
			tmp, err := GetDirAllEntryPaths(path, incl)
			if err != nil {
				return nil, err
			}
			paths = append(paths, tmp...)
			continue
		}
		paths = append(paths, path)
	}
	return paths, nil
}

// GetDirAllEntryPathsFollowSymlink gets all the file or dir paths in the specified directory recursively.
func GetDirAllEntryPathsFollowSymlink(dirname string, incl bool) ([]string, error) {
	// Remove the trailing path separator if dirname has.
	dirname = strings.TrimSuffix(dirname, string(os.PathSeparator))

	infos, err := ioutil.ReadDir(dirname)
	if err != nil {
		return nil, err
	}

	paths := make([]string, 0, len(infos))
	// Include current dir.
	if incl {
		paths = append(paths, dirname)
	}

	for _, info := range infos {
		path := dirname + string(os.PathSeparator) + info.Name()
		realInfo, err := os.Stat(path)
		if err != nil {
			return nil, err
		}
		if realInfo.IsDir() {
			tmp, err := GetDirAllEntryPathsFollowSymlink(path, incl)
			if err != nil {
				return nil, err
			}
			paths = append(paths, tmp...)
			continue
		}
		paths = append(paths, path)
	}
	return paths, nil
}

我们测试一下。

func main() {
	fmt.Println("not follow symlink:")
	paths, _ := GetDirAllEntryPaths("dir/", true)
	for _, path := range paths {
		fmt.Println(path)
	}

	fmt.Println("\nfollow symlink:")
	paths, _ = GetDirAllEntryPathsFollowSymlink("dir/", true)
	for _, path := range paths {
		fmt.Println(path)
	}
}

运行输出:

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 直接使用。

package main

import (
	"github.com/dablelv/go-huge-util/file"
)

func main() {
	// 获取目录下所有文件和子目录名称(不会递归)。
	names, _ := file.ListDir("dir")

	// 递归获取目录下所有文件路径(不解析符号链接)
	paths, _ := file.GetDirAllEntryPaths("dir", false)
	// 递归获取目录下所有文件和目录路径(不解析符号链接)
	paths, _ = file.GetDirAllEntryPaths("dir", true)

	// 递归获取目录下所有文件路径(解析符号链接)
	paths, _ = file.GetDirAllEntryPathsFollowSymlink("dir", false)
	// 递归获取目录下所有文件与目录路径(解析符号链接)
	paths, _ = file.GetDirAllEntryPathsFollowSymlink("dir/", true)
}

欢迎大家 Star & PR。

参考文献

io/ioutil - Go Packages

总结

到此这篇关于Golang递归获取目录下所有文件的文章就介绍到这了,更多相关Golang递归获取目录所有文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go 高效截取字符串的一些思考

    Go 高效截取字符串的一些思考

    这篇文章主要介绍了Go 高效截取字符串的一些思考,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • 详解Golang如何动态获取配置文件

    详解Golang如何动态获取配置文件

    项目中经常获取一些常用配置文件,当有配置文件中的参数被修改后,如何的实时获取配置文件就很关键了,下面小编就来讲讲如何利用viper实时获取配置文件吧
    2023-08-08
  • GoFrame gredis缓存DoVar及Conn连接对象的自动序列化

    GoFrame gredis缓存DoVar及Conn连接对象的自动序列化

    这篇文章主要为大家介绍了GoFrame gredis干货DoVar Conn连接对象自动序列化详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 浅谈golang并发操作变量安全的问题

    浅谈golang并发操作变量安全的问题

    这篇文章主要介绍了浅谈golang并发操作变量安全的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go语言异常处理(Panic和recovering)用法详解

    Go语言异常处理(Panic和recovering)用法详解

    异常处理是程序健壮性的关键,往往开发人员的开发经验的多少从异常部分处理上就能得到体现。Go语言中没有Try Catch Exception机制,但是提供了panic-and-recover机制,本文就来详细讲讲他们的用法
    2022-07-07
  • Go语言实现JSON解析的神器详解

    Go语言实现JSON解析的神器详解

    php转go是大趋势,越来越多公司的php服务都在用go进行重构,重构过程中,会发现php的json解析操作是真的香。本文和大家分享了一个Go语言实现JSON解析的神器,希望对大家有所帮助
    2023-01-01
  • Golang实现for循环运行超时后自动退出的方法

    Golang实现for循环运行超时后自动退出的方法

    for循环对大家来说应该都不陌生,对于golang来说更是必不可少,所以下面这篇文章就来给大家介绍了关于Golang如何实现for循环运行一段时间超时后自动退出的相关资料,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧。
    2017-11-11
  • Go项目分层下的最佳error处理方式分享

    Go项目分层下的最佳error处理方式分享

    这篇文章主要来和大家一起探讨 Go 项目分层下的最佳 error 处理方式,准备好了吗?准备一杯你最喜欢的饮料或茶,随着本文一探究竟吧
    2023-06-06
  • Go语言基础变量的声明及初始化示例详解

    Go语言基础变量的声明及初始化示例详解

    这篇文章主要为大家介绍了Go语言基础变量的声明及初始化示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步,早日升职加薪
    2021-11-11
  • Go语言学习之goroutine详解

    Go语言学习之goroutine详解

    Goroutine是建立在线程之上的轻量级的抽象。它允许我们以非常低的代价在同一个地址空间中并行地执行多个函数或者方法,这篇文章主要介绍了Go语言学习之goroutine的相关知识,需要的朋友可以参考下
    2020-02-02

最新评论