【问题标题】:List directory in GoGo中的列表目录
【发布时间】:2013-01-18 02:19:03
【问题描述】:

我一直试图弄清楚如何在 Go 中简单地列出单个目录中的文件和文件夹。

我找到了filepath.Walk,但它会自动进入子目录,这是我不想要的。我所有的其他搜索都没有好转。

我确信这个功能是存在的,但是真的很难找到。如果有人知道我应该看哪里,请告诉我。谢谢。

【问题讨论】:

    标签: go


    【解决方案1】:

    您可以尝试使用io/ioutil 包中的ReadDir 函数。根据文档:

    ReadDir 读取由 dirname 命名的目录并返回已排序的目录条目列表。

    生成的切片包含os.FileInfo 类型,这些类型提供here 列出的方法。这是一个基本示例,列出了当前目录中所有内容的名称(包括文件夹但没有特别标记 - 您可以使用 IsDir() 方法检查项目是否为文件夹):

    package main
    
    import (
        "fmt"
        "io/ioutil"
         "log"
    )
    
    func main() {
        files, err := ioutil.ReadDir("./")
        if err != nil {
            log.Fatal(err)
        }
     
        for _, f := range files {
                fmt.Println(f.Name())
        }
    }
    

    【讨论】:

    • 如果您只想要目录内容的名称并且速度至关重要,请注意使用Readdirnames数量级更快(对我来说大约快 20 倍)
    • @SquattingSlavInTracksuit:我在这里将您的评论提升为答案,因为我当时没有评论权限。如果您愿意回答并获得荣誉,LMK。
    • @SquattingSlavInTracksuit - 这只是一个数量级:P
    • 从 go 1.16 开始,io/ioutil 已被弃用。而不是 ioutil.ReadDir() 你应该使用 os.ReadDir()
    【解决方案2】:

    我们可以使用各种 golang 标准库函数来获取文件系统上文件夹内的文件列表。

    1. filepath.Walk
    2. ioutil.ReadDir
    3. os.File.Readdir
    
    package main
    
    import (
        "fmt"
        "io/ioutil"
        "log"
        "os"
        "path/filepath"
    )
    
    func main() {
        var (
            root  string
            files []string
            err   error
        )
    
        root := "/home/manigandan/golang/samples"
        // filepath.Walk
        files, err = FilePathWalkDir(root)
        if err != nil {
            panic(err)
        }
        // ioutil.ReadDir
        files, err = IOReadDir(root)
        if err != nil {
            panic(err)
        }
        //os.File.Readdir
        files, err = OSReadDir(root)
        if err != nil {
            panic(err)
        }
    
        for _, file := range files {
            fmt.Println(file)
        }
    }
    
    1. 使用 filepath.Walk

    path/filepath 包提供了一种方便的方法来扫描所有文件 在一个目录中,它会自动扫描目录中的每个子目录 目录。

    func FilePathWalkDir(root string) ([]string, error) {
        var files []string
        err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
            if !info.IsDir() {
                files = append(files, path)
            }
            return nil
        })
        return files, err
    }
    
    1. 使用 ioutil.ReadDir

    ioutil.ReadDir 读取 dirname 命名的目录并返回一个 按文件名排序的目录条目列表。

    func IOReadDir(root string) ([]string, error) {
        var files []string
        fileInfo, err := ioutil.ReadDir(root)
        if err != nil {
            return files, err
        }
    
        for _, file := range fileInfo {
            files = append(files, file.Name())
        }
        return files, nil
    }
    
    1. 使用 os.File.Readdir

    Readdir 读取与文件关联的目录的内容,并 返回最多 n 个 FileInfo 值的切片,如将由 Lstat,按目录顺序。对同一文件的后续调用将 产生更多的 FileInfos。

    func OSReadDir(root string) ([]string, error) {
        var files []string
        f, err := os.Open(root)
        if err != nil {
            return files, err
        }
        fileInfo, err := f.Readdir(-1)
        f.Close()
        if err != nil {
            return files, err
        }
    
        for _, file := range fileInfo {
            files = append(files, file.Name())
        }
        return files, nil
    }
    

    基准测试结果。

    获取有关此Blog Post 的更多详细信息

    【讨论】:

    • 这里最完整的答案。值得注意的是,这个基准测试没有报告内存使用或分配。更快的实现可能会使用更多的内存。测试人员计算机上的 CPU 内核数量也可能会伤害/帮助并发filepath.Walk。此外,filepath.Walk 支持递归体面,而os.File.Readdirioutil.ReadDir 不支持。
    • 有人可以向我解释如何阅读/解释基准测试结果吗?
    • @xuiqzy ns/op = 每次操作纳秒,所以越小越好。但正如@xeoncross 所提到的,Walk 在并发环境中可能会更好,并且在具有大量文件的环境中使用的内存可能会少得多。
    • 来自 ioutil.ReadDir 的 Go 源代码:“从 Go 1.16 开始,os.ReadDir 是更有效和正确的选择:它返回 fs.DirEntry 的列表而不是 fs.FileInfo,并且它在读取目录中途出错的情况下返回部分结果。"完整的 ioutil 包函数应根据文档替换为 ioos 包中的函数。
    【解决方案3】:

    更简单,使用path/filepath:

    package main    
    
    import (
        "fmt"
        "log"
        "path/filepath"
    )
    
    func main() {
        files, err := filepath.Glob("*")
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(files) // contains a list of all files in the current directory
    }
    

    【讨论】:

    • 请注意Glob ignores file system errors such as I/O errors reading directories. The only possible returned error is ErrBadPattern, when pattern is malformed.
    • 在使用之前一定要了解 Glob 的作用。 golang.org/pkg/path/filepath/#Glob
    【解决方案4】:

    ioutil.ReadDir 是一个不错的发现,但是如果您单击并查看源代码,您会发现它调用了方法 Readdir of os.File。如果您对目录顺序没有问题并且不需要对列表进行排序,那么这个 Readdir 方法就是您所需要的。

    【讨论】:

      【解决方案5】:

      从 Go 1.16 开始,您可以使用 os.ReadDir 函数。

      func ReadDir(name string) ([]DirEntry, error)

      它读取给定目录并返回一个DirEntry 切片,其中包含按文件名排序的目录条目。

      这是一个乐观函数,因此,当读取目录条目时发生错误时,它会尝试返回一个切片,其中包含错误前的文件名。

      package main
      
      import (
          "fmt"
          "log"
          "os"
      )
      
      func main() {
          files, err := os.ReadDir(".")
          if err != nil {
              log.Fatal(err)
          }
      
          for _, file := range files {
              fmt.Println(file.Name())
          }
      }
      

      感兴趣的:Go 1.17(2021 年第三季度)包括 fs.FileInfoToDirEntry()

      func FileInfoToDirEntry(info FileInfo) DirEntry
      

      FileInfoToDirEntry 返回一个 DirEntry,它返回来自 info 的信息。
      如果info 为nil,FileInfoToDirEntry 返回nil。


      背景

      Go 1.16(2021 年第一季度)将提出,CL 243908CL 243914ReadDir function,基于 FS interface

      // An FS provides access to a hierarchical file system.
      //
      // The FS interface is the minimum implementation required of the file system.
      // A file system may implement additional interfaces,
      // such as fsutil.ReadFileFS, to provide additional or optimized functionality.
      // See io/fsutil for details.
      type FS interface {
          // Open opens the named file.
          //
          // When Open returns an error, it should be of type *PathError
          // with the Op field set to "open", the Path field set to name,
          // and the Err field describing the problem.
          //
          // Open should reject attempts to open names that do not satisfy
          // ValidPath(name), returning a *PathError with Err set to
          // ErrInvalid or ErrNotExist.
          Open(name string) (File, error)
      }
      

      这允许"os: add ReadDir method for lightweight directory reading":
      commit a4ede9f:

      // ReadDir reads the contents of the directory associated with the file f
      // and returns a slice of DirEntry values in directory order.
      // Subsequent calls on the same file will yield later DirEntry records in the directory.
      //
      // If n > 0, ReadDir returns at most n DirEntry records.
      // In this case, if ReadDir returns an empty slice, it will return an error explaining why.
      // At the end of a directory, the error is io.EOF.
      //
      // If n <= 0, ReadDir returns all the DirEntry records remaining in the directory.
      // When it succeeds, it returns a nil error (not io.EOF).
      func (f *File) ReadDir(n int) ([]DirEntry, error) 
      
      // A DirEntry is an entry read from a directory (using the ReadDir method).
      type DirEntry interface {
          // Name returns the name of the file (or subdirectory) described by the entry.
          // This name is only the final element of the path, not the entire path.
          // For example, Name would return "hello.go" not "/home/gopher/hello.go".
          Name() string
          
          // IsDir reports whether the entry describes a subdirectory.
          IsDir() bool
          
          // Type returns the type bits for the entry.
          // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
          Type() os.FileMode
          
          // Info returns the FileInfo for the file or subdirectory described by the entry.
          // The returned FileInfo may be from the time of the original directory read
          // or from the time of the call to Info. If the file has been removed or renamed
          // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
          // If the entry denotes a symbolic link, Info reports the information about the link itself,
          // not the link's target.
          Info() (FileInfo, error)
      }
      

      src/os/os_test.go#testReadDir() 说明了它的用法:

          file, err := Open(dir)
          if err != nil {
              t.Fatalf("open %q failed: %v", dir, err)
          }
          defer file.Close()
          s, err2 := file.ReadDir(-1)
          if err2 != nil {
              t.Fatalf("ReadDir %q failed: %v", dir, err2)
          }
      

      Ben Hoyt 指出 in the comments 到 Go 1.16 os.ReadDir

      os.ReadDir(path string) ([]os.DirEntry, error),无需Open 即可直接调用。
      因此,您可能可以将其缩短为 os.ReadDir,因为这是大多数人会调用的具体函数。

      commit 3d913a9(2020 年 12 月):

      os:从io/ioutil添加ReadFileWriteFileCreateTemp(原为TempFile)、MkdirTemp(原为TempDir

      io/ioutil 是定义不明确的助手集合。

      Proposal #40025 将通用 I/O 助手移出到 io。 proposal #42026 的这个 CL 将特定于操作系统的助手移动到 os, 弃用整个 io/ioutil 包。

      os.ReadDir 返回 []DirEntry,与 ioutil.ReadDir[]FileInfo 形成对比。
      (提供返回 []DirEntry 的助手是此更改的主要动机之一。)

      【讨论】:

      • 是的,不过请注意,作为 1.16 工作的一部分,他们还添加了 os.ReadDir(path string) ([]os.DirEntry, error),您可以直接调用它而无需 Open 跳舞。因此,您可以将其缩短为 os.ReadDir,因为这是大多数人会调用的具体函数。
      • @BenHoyt 好点,谢谢。我已将您的评论以及其他文档链接包含在答案中以提高知名度。
      【解决方案6】:

      根据您的描述,您可能想要的是os.Readdirnames

      func (f *File) Readdirnames(n int) (names []string, err error)

      Readdirnames 读取与文件关联的目录的内容,并按目录顺序返回目录中最多 n 个文件名的切片。对同一文件的后续调用将产生更多名称。

      ...

      如果 n

      片段:

      file, err := os.Open(path)
      if err != nil {
          return err
      }
      defer file.Close()
      names, err := file.Readdirnames(0)
      if err != nil {
          return err
      }
      fmt.Println(names)
      

      归功于SquattingSlavInTracksuitcomment;如果可以的话,我会建议将他们的评论推广到答案。

      【讨论】:

        【解决方案7】:

        使用Readdirnames递归打印目录中所有文件的完整示例

        package main
        
        import (
            "fmt"
            "os"
        )
        
        func main() {
            path := "/path/to/your/directory"
            err := readDir(path)
            if err != nil {
                panic(err)
            }
        }
        
        func readDir(path string) error {
            file, err := os.Open(path)
            if err != nil {
                return err
            }
            defer file.Close()
            names, _ := file.Readdirnames(0)
            for _, name := range names {
                filePath := fmt.Sprintf("%v/%v", path, name)
                file, err := os.Open(filePath)
                if err != nil {
                    return err
                }
                defer file.Close()
                fileInfo, err := file.Stat()
                if err != nil {
                    return err
                }
                fmt.Println(filePath)
                if fileInfo.IsDir() {
                    readDir(filePath)
                }
            }
            return nil
        }
        

        【讨论】:

          猜你喜欢
          • 2018-09-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-09-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多