golang遍历文件夹生成目录树

3,030 阅读4分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。

golang遍历文件夹,简单遍历的话,可以使用 path/filepath包下的func Walk(root string, fn WalkFunc) error配合 WalkFunc

如果需求复杂一些,比如:

  • 忽略某些目录
  • 忽略某些文件
  • 根据需要遍历子目录或者不遍历子目录等。

使用 WalkWalkFunc感觉有所受限,于是自己折腾了个遍历方法。现在实现得还比较简单,等有时间再慢慢扩展。

本文相关代码: hellogo/explorer at main · bettersun/hellogo (github.com)

golang遍历文件夹生成目录树

遍历文件夹构建一个目录树,现在只是简单的遍历加上简单的附加选项。

  1. 最终结果是一个目录树,需要先定义一个树结构用的节点 struct。

    // Node 树节点
    type Node struct {
      Name     string  `json:"name"`     // 目录(或文件)名
      Path     string  `json:"path"`     // 目录(或文件)完整路径
      Children []*Node `json:"children"` // 目录下的文件或子目录
      IsDir    bool    `json:"isDir"`    // 是否为目录 true: 是目录 false: 不是目录
    }
    

    如果需要其它信息,可以继续添加,比如 添加 id给前端用,或者添加权限 之类的。

  2. 需要定义一个遍历选项,用来设置遍历时的条件。

    // 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 还需要各写一套,然后也没调查出来好的写法,后面有时间再补上)。

  3. 遍历入口和递归遍历。

    遍历入口函数:

    // 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
    }
    

现在只是简单的遍历,还能继续扩展。

比如:

  • 遍历选项指定关键字,可以实现查找功能。
  • 遍历选项可以加上正则表达式或其它匹配模式,实现高级查找功能。
  • 遍历选项的忽略目录和忽略文件也可以指定关键字