Node.js fs.readdir 递归目录搜索

Posted

技术标签:

【中文标题】Node.js fs.readdir 递归目录搜索【英文标题】:Node.js fs.readdir recursive directory search 【发布时间】:2011-08-15 05:13:49 【问题描述】:

关于使用 fs.readdir 进行异步目录搜索的任何想法?我意识到我们可以引入递归并使用下一个要读取的目录调用读取目录函数,但我有点担心它不是异步的......

有什么想法吗?我看过node-walk,这很棒,但不像 readdir 那样只给我数组中的文件。虽然

寻找类似...的输出

['file1.txt', 'file2.txt', 'dir/file3.txt']

【问题讨论】:

【参考方案1】:

另一个答案,但这次使用 TypeScript:

/**
 * Recursively walk a directory asynchronously and obtain all file names (with full path).
 *
 * @param dir Folder name you want to recursively process
 * @param done Callback function, returns all files with full path.
 * @param filter Optional filter to specify which files to include, 
 *   e.g. for json files: (f: string) => /.json$/.test(f)
 */
const walk = (
  dir: string,
  done: (err: Error | null, results ? : string[]) => void,
  filter ? : (f: string) => boolean
) => 
  let results: string[] = [];
  fs.readdir(dir, (err: Error, list: string[]) => 
    if (err) 
      return done(err);
    
    let pending = list.length;
    if (!pending) 
      return done(null, results);
    
    list.forEach((file: string) => 
      file = path.resolve(dir, file);
      fs.stat(file, (err2, stat) => 
        if (stat && stat.isDirectory()) 
          walk(file, (err3, res) => 
            if (res) 
              results = results.concat(res);
            
            if (!--pending) 
              done(null, results);
            
          , filter);
         else 
          if (typeof filter === 'undefined' || (filter && filter(file))) 
            results.push(file);
          
          if (!--pending) 
            done(null, results);
          
        
      );
    );
  );
;

【讨论】:

【参考方案2】:

对于Node 10.3+,这里是for-await 解决方案:

#!/usr/bin/env node

const FS = require('fs');
const Util = require('util');
const readDir = Util.promisify(FS.readdir);
const Path = require('path');

async function* readDirR(path) 
    const entries = await readDir(path,withFileTypes:true);
    for(let entry of entries) 
        const fullPath = Path.join(path,entry.name);
        if(entry.isDirectory()) 
            yield* readDirR(fullPath);
         else 
            yield fullPath;
        
    


async function main() 
    const start = process.hrtime.bigint();
    for await(const file of readDirR('/mnt/home/media/Unsorted')) 
        console.log(file);
    
    console.log((process.hrtime.bigint()-start)/1000000n);


main().catch(err => 
    console.error(err);
);

此解决方案的好处是您可以立即开始处理结果;例如读取我的媒体目录中的所有文件需要 12 秒,但如果我这样做,我可以在几毫秒内得到第一个结果。

【讨论】:

【参考方案3】:

异步

const fs = require('fs')
const path = require('path')

const readdir = (p, done, a = [], i = 0) => fs.readdir(p, (e, d = []) =>
  d.map(f => readdir(a[a.push(path.join(p, f)) - 1], () =>
    ++i == d.length && done(a), a)).length || done(a))

readdir(__dirname, console.log)

同步

const fs = require('fs')
const path = require('path')

const readdirSync = (p, a = []) => 
  if (fs.statSync(p).isDirectory())
    fs.readdirSync(p).map(f => readdirSync(a[a.push(path.join(p, f)) - 1], a))
  return a


console.log(readdirSync(__dirname))

异步可读

function readdir (currentPath, done, allFiles = [], i = 0) 
  fs.readdir(currentPath, function (e, directoryFiles = []) 
    if (!directoryFiles.length)
      return done(allFiles)
    directoryFiles.map(function (file) 
      var joinedPath = path.join(currentPath, file)
      allFiles.push(joinedPath)
      readdir(joinedPath, function () 
        i = i + 1
        if (i == directoryFiles.length)
          done(allFiles)
      , allFiles)
    )
  )


readdir(__dirname, console.log)

注意:两个版本都将遵循符号链接(与原始 fs.readdir 相同)

【讨论】:

【参考方案4】:

TypeScript 中使用 Array.flat() 处理嵌套返回的基于 Promise 的递归解决方案。

import  resolve  from 'path'
import  Dirent  from 'fs'
import * as fs from 'fs'

function getFiles(root: string): Promise<string[]> 
 return fs.promises
   .readdir(root,  withFileTypes: true )
   .then(dirents => 
      const mapToPath = (r: string) => (dirent: Dirent): string => resolve(r, dirent.name)
      const directoryPaths = dirents.filter(a => a.isDirectory()).map(mapToPath(root))
      const filePaths = dirents.filter(a => a.isFile()).map(mapToPath(root))

     return Promise.all<string>([
       ...directoryPaths.map(a => getFiles(a, include)).flat(),
       ...filePaths.map(a => Promise.resolve(a))
     ]).then(a => a.flat())
  )

【讨论】:

【参考方案5】:

想要同步替代已接受答案的人(我知道我愿意):

var fs = require('fs');
var path = require('path');
var walk = function(dir) 
    let results = [], err = null, list;
    try 
        list = fs.readdirSync(dir)
     catch(e) 
        err = e.toString();
    
    if (err) return err;
    var i = 0;
    return (function next() 
        var file = list[i++];

        if(!file) return results;
        file = path.resolve(dir, file);
        let stat = fs.statSync(file);
        if (stat && stat.isDirectory()) 
          let res = walk(file);
          results = results.concat(res);
          return next();
         else 
          results.push(file);
           return next();
        

    )();

;

console.log(
    walk("./")
)

【讨论】:

【参考方案6】:

只是简单的步行

let pending = [baseFolderPath]
function walk () 
    pending.shift();
    // do stuffs width pending[0] and change pending items
    if (pending[0]) walk(pending[0])

walk(pending[0])

【讨论】:

【参考方案7】:

qwtel 的 answer 变体,在 TypeScript

import  resolve  from 'path';
import  readdir  from 'fs/promises';

async function* getFiles(dir: string): AsyncGenerator<string> 
    const entries = await readdir(dir,  withFileTypes: true );
    for (const entry of entries) 
        const res = resolve(dir, entry.name);
        if (entry.isDirectory()) 
            yield* getFiles(res);
         else 
            yield res;
        
    

【讨论】:

【参考方案8】:

有一个名为cup-readdir 的新模块可以非常快速地递归搜索目录。在处理深层目录结构时,它使用异步 Promise 并优于许多流行的模块。

它可以返回一个数组中的所有文件并按属性对它们进行排序,但缺少文件过滤和进入符号链接目录等功能。这对于您只想从目录中获取每个文件的大型项目可能很有用。这是他们项目主页的link。

【讨论】:

【参考方案9】:

这里是一个简单的同步递归解决方案

const fs = require('fs')

const getFiles = path => 
    const files = []
    for (const file of fs.readdirSync(path)) 
        const fullPath = path + '/' + file
        if(fs.lstatSync(fullPath).isDirectory())
            getFiles(fullPath).forEach(x => files.push(file + '/' + x))
        else files.push(file)
    
    return files

用法:

const files = getFiles(process.cwd())

console.log(files)

您可以异步编写它,但没有必要。只需确保输入目录存在且可访问。

【讨论】:

正在寻找一个简单的同步解决方案来加载一些配置,这满足了我的需求。 forEach 中的基本路径应该是 fullPath 以便以后可以使用 fs.readFileSync(file) 轻松读取文件,所以 x =&gt; files.push(fullPath + '/' + x)) 是的,您可以随意调整它,我认为 fs.readFileSync 可以理解相对路径,因此取决于您从哪里运行它,但可能甚至不需要更改。跨度> 【参考方案10】:

简单,基于异步承诺


const fs = require('fs/promises');
const getDirRecursive = async (dir) => 
    try 
        const items = await fs.readdir(dir);
        let files = [];
        for (const item of items) 
            if ((await fs.lstat(`$dir/$item`)).isDirectory()) files = [...files, ...(await getDirRecursive(`$dir/$item`))];
            else files.push(file: item, path: `$dir/$item`, parents: dir.split("/"));
        
        return files;
     catch (e) 
        return e
    
;

用法:await getDirRecursive("./public");

【讨论】:

【参考方案11】:

基于现代承诺的读取目录递归版本:

const fs = require('fs');
const path = require('path');

const readDirRecursive = async (filePath) => 
    const dir = await fs.promises.readdir(filePath);
    const files = await Promise.all(dir.map(async relativePath => 
        const absolutePath = path.join(filePath, relativePath);
        const stat = await fs.promises.lstat(absolutePath);

        return stat.isDirectory() ? readDirRecursive(absolutePath) : absolutePath;
    ));

    return files.flat();

【讨论】:

以上是关于Node.js fs.readdir 递归目录搜索的主要内容,如果未能解决你的问题,请参考以下文章

Node.js fs.readdir 递归目录搜索

fs.readdir 递归搜索深度=1

Node.js 内置模块fs的readdir方法 查看某个文件夹里面包含的文件内容

Node.js——fs模块(文件系统),创建删除目录(文件),读取写入文件流

在 node.js 中读取文件和读取目录

Node.js之文件及文件流(fs,path,buffer,stream)