「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。
golang遍历文件夹,简单遍历的话,可以使用 path/filepath
包下的func Walk(root string, fn WalkFunc) error
配合 WalkFunc
。
如果需求复杂一些,比如:
- 忽略某些目录
- 忽略某些文件
- 根据需要遍历子目录或者不遍历子目录等。
使用 Walk
和 WalkFunc
感觉有所受限,于是自己折腾了个遍历方法。现在实现得还比较简单,等有时间再慢慢扩展。
本文相关代码: hellogo/explorer at main · bettersun/hellogo (github.com)
golang遍历文件夹生成目录树
遍历文件夹构建一个目录树,现在只是简单的遍历加上简单的附加选项。
-
最终结果是一个目录树,需要先定义一个树结构用的节点 struct。
// Node 树节点 type Node struct { Name string `json:"name"` // 目录(或文件)名 Path string `json:"path"` // 目录(或文件)完整路径 Children []*Node `json:"children"` // 目录下的文件或子目录 IsDir bool `json:"isDir"` // 是否为目录 true: 是目录 false: 不是目录 }
如果需要其它信息,可以继续添加,比如 添加 id给前端用,或者添加权限 之类的。
-
需要定义一个遍历选项,用来设置遍历时的条件。
// Option 遍历选项 type Option struct { RootPath []string `yaml:"rootPath"` // 目标根目录 SubFlag bool `yaml:"subFlag"` // 遍历子目录标志 true: 遍历 false: 不遍历 IgnorePath []string `yaml:"ignorePath"` // 忽略目录 IgnoreFile []string `yaml:"ignoreFile"` // 忽略文件 }
这里的目标根目录使用的切片,这样可以同时遍历多个目录(暂时没有对应多个目录之间有相关包含关系的处理)。
忽略目录和忽略文件也是使用的切片,可以指定多个忽略的目录(比如: .git, .svn 这样的目录)和多个忽略的文件(比如: .DS_Store 这样的文件)
如果需要其它遍历选项,可以继续添加,在遍历方法里加上对应选项的处理即可。
(本来还想添加个是否遍历隐藏文件的标志,结果发现 Mac 和 Windows 还需要各写一套,然后也没调查出来好的写法,后面有时间再补上)。
-
遍历入口和递归遍历。
遍历入口函数:
// Explorer 遍历多个目录 // option : 遍历选项 // tree : 遍历结果 func Explorer(option Option) (Node, error) { // 根节点 var root Node // 多个目录搜索 for _, p := range option.RootPath { // 空目录跳过 if strings.TrimSpace(p) == "" { continue } var child Node // 目录路径 child.Path = p // 递归 explorerRecursive(&child, &option) root.Children = append(root.Children, &child) } return root, nil }
定义一个根节点,然后对遍历目标根目录进行递归遍历,目标根目录的遍历结果添加到根节点的孩子里。
递归遍历函数:
// 递归遍历目录 // node : 目录节点 // option : 遍历选项 func explorerRecursive(node *Node, option *Option) { // 节点的信息 p, err := os.Stat(node.Path) if err != nil { log.Println(err) return } // 目录(或文件)名 node.Name = p.Name() // 是否为目录 node.IsDir = p.IsDir() // 非目录,返回 if !p.IsDir() { return } // 目录中的文件和子目录 sub, err := ioutil.ReadDir(node.Path) if err != nil { info := "目录不存在,或打开错误。" log.Printf("%v: %v", info, err) return } for _, f := range sub { tmp := path.Join(node.Path, f.Name()) var child Node // 完整子目录 child.Path = tmp // 是否为目录 child.IsDir = f.IsDir() // 目录 if f.IsDir() { //查找子目录 if option.SubFlag { // 不在忽略目录中的目录,进行递归查找 if !IsInSlice(option.IgnorePath, f.Name()) { node.Children = append(node.Children, &child) explorerRecursive(&child, option) } } } else { // 文件 // 非忽略文件,添加到结果中 if !IsInSlice(option.IgnoreFile, f.Name()) { child.Name = f.Name() node.Children = append(node.Children, &child) } } } }
递归遍历函数的第一个参数节点,需要使用指针,这样在函数内部对节点信息的改变才能起作用。
第二个参数遍历选项,可以不使用指针,用指针效率多少能高点儿吧。
对于节点,获取信息后设置到节点上。
如果节点是目录的话,获取节点下的子目录和文件,然后再遍历。
对于节点下的子目录,如果不在忽略目录中,先添加到遍历结果,再进行递归遍历。
对于节点下的文件,如果不在忽略文件中,添加到遍历结果。这里没有严谨的错误处理,只是简单的 打印错误信息。
上面的函数中用了一个小函数,来判断忽略目录和忽略文件。
// IsInSlice 判断目标字符串是否是在切片中 func IsInSlice(slice []string, s string) bool { if len(slice) == 0 { return false } isIn := false for _, f := range slice { if f == s { isIn = true break } } return isIn }
现在只是简单的遍历,还能继续扩展。
比如:
- 遍历选项指定关键字,可以实现查找功能。
- 遍历选项可以加上正则表达式或其它匹配模式,实现高级查找功能。
- 遍历选项的忽略目录和忽略文件也可以指定关键字