Golang递归获取目录下所有文件方法实例
恋喵大鲤鱼 人气:01.问题
如果我想获取一个目录下的所有文件列表,使用 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 -> ../ziptree 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/ziplnfollow 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。
参考文献
总结
加载全部内容