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

cra*_*awf 242 readdir node.js

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

有任何想法吗?我看过node-walk这很棒,但是不给我一个数组中的文件,比如readdir.虽然

寻找像...的输出

['file1.txt', 'file2.txt', 'dir/file3.txt']
Run Code Online (Sandbox Code Playgroud)

chj*_*hjj 361

基本上有两种方法可以实现这一点.在异步环境中,您会注意到有两种循环:串行和并行.串行循环在进入下一次迭代之前等待一次迭代完成 - 这保证循环的每次迭代按顺序完成.在并行循环中,所有迭代都是同时启动的,并且可以在另一个迭代之前完成,但是,它比串行循环快得多.因此,在这种情况下,使用并行循环可能更好,因为无论步行完成的顺序无关紧要,只要它完成并返回结果(除非您按顺序要求它们).

并行循环看起来像这样:

var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var pending = list.length;
    if (!pending) return done(null, results);
    list.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            if (!--pending) done(null, results);
          });
        } else {
          results.push(file);
          if (!--pending) done(null, results);
        }
      });
    });
  });
};
Run Code Online (Sandbox Code Playgroud)

串行循环看起来像这样:

var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};
Run Code Online (Sandbox Code Playgroud)

并在您的主目录中测试它(警告:如果您的主目录中有很多东西,结果列表将是巨大的):

walk(process.env.HOME, function(err, results) {
  if (err) throw err;
  console.log(results);
});
Run Code Online (Sandbox Code Playgroud)

编辑:改进的例子.

  • `file = dir +'/'+ file;`这不推荐.您应该使用:`var path = require('path'); file = path.resolve(dir,file);` (27认同)
  • 我贬低了,因为当你在2011年第一次写回来时,你的答案很棒,但在2014年,人们使用开源模块,自己编写更少的代码,并为他们和许多其他人所依赖的模块做出贡献.例如,尝试[node-dir](https://github.com/fshost/node-dir)使用以下代码行获得@crawf所需的输出:`require('node-dir').files( __dirname,function(err,files){console.log(files);});` (17认同)
  • 请注意,上面chjj的"并行循环"答案在行走空文件夹时会出现错误.修复方法是:var pending = list.length; if(!pending)done(null,results); //添加这一行!list.forEach(function(file){... (10认同)
  • 添加一条注释,指出高度赞扬的评论建议的 `node-dir` 软件包已经 5 年没有更新过(于 2021 年 11 月 29 日检查)。 (7认同)
  • @onetrickpony因为如果你使用`path.resolve(...)`你会得到一个正确的路径,无论你是在Windows还是Unix :)意味着你会得到类似`C:\\ some \\ foo \\ path `在Windows上和Unix系统上的`/ some/foo/path` (6认同)
  • 对于任何对`! - `语法感到困惑的人,[有人问过这个问题](http://stackoverflow.com/questions/34323527/what-does-do-in-javascript) (5认同)
  • 您使用“fs”而不是“fs.promises”有什么特殊原因吗?`fs.promises` 不是更好吗? (2认同)
  • 在符号链接的情况下是否有可能陷入无限循环? (2认同)

小智 99

为了防止任何人发现它有用,我还整理了一个同步版本.

var walk = function(dir) {
    var results = [];
    var list = fs.readdirSync(dir);
    list.forEach(function(file) {
        file = dir + '/' + file;
        var stat = fs.statSync(file);
        if (stat && stat.isDirectory()) { 
            /* Recurse into a subdirectory */
            results = results.concat(walk(file));
        } else { 
            /* Is a file */
            results.push(file);
        }
    });
    return results;
}
Run Code Online (Sandbox Code Playgroud)

提示:过滤时使用较少的资源.在此功能本身内过滤.例如,results.push(file);用以下代码替换.根据需要调整:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案,除了缺少分号! (58认同)
  • 考虑使用file = require("path").join(dir,file) (14认同)
  • @mpen半冒号是多余的 (12认同)

qwt*_*tel 88

这个使用了节点8中可用的最新的buzzwordy功能,包括Promises,util/promisify,destructuring,async-await,map + reduce等等,让你的同事在试图弄清楚正在进行.

节点8+

没有外部依赖.

const { promisify } = require('util');
const { resolve } = require('path');
const fs = require('fs');
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);

async function getFiles(dir) {
  const subdirs = await readdir(dir);
  const files = await Promise.all(subdirs.map(async (subdir) => {
    const res = resolve(dir, subdir);
    return (await stat(res)).isDirectory() ? getFiles(res) : res;
  }));
  return files.reduce((a, f) => a.concat(f), []);
}
Run Code Online (Sandbox Code Playgroud)

用法:

getFiles(__dirname)
  .then(files => console.log(files))
  .catch(e => console.error(e));
Run Code Online (Sandbox Code Playgroud)

节点10+

更新了节点10+以及更多的whizbang:

const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  const files = await Promise.all(dirents.map((dirent) => {
    const res = resolve(dir, dirent.name);
    return dirent.isDirectory() ? getFiles(res) : res;
  }));
  return Array.prototype.concat(...files);
}
Run Code Online (Sandbox Code Playgroud)

节点11+

如果你想让每个人都完全放心,你可以使用异步迭代器来使用以下版本.除了非常酷,它还允许消费者一次一个地提取结果,使其更适合真正大的目录.

const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function* getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  for (const dirent of dirents) {
    const res = resolve(dir, dirent.name);
    if (dirent.isDirectory()) {
      yield* getFiles(res);
    } else {
      yield res;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

用法已更改,因为返回类型现在是异步迭代器而不是promise

(async () => {
  for await (const f of getFiles('.')) {
    console.log(f);
  }
})()
Run Code Online (Sandbox Code Playgroud)

  • `subdir`和`subdirs`的命名是误导性的,因为那些可能实际上是文件(我建议使用`itemInDir`或`item_in_dir`或甚至简称`item`),但这个解决方案感觉比接受的更干净而且代码要少得多.我也没有发现它比接受的答案中的代码复杂得多.+1 (5认同)
  • 你可以通过使用 `require(fs).promises` 并完全删除 `util.promisify` 来使这更加精彩。我个人将 fs 别名为 fs.promises。 (2认同)
  • 我们可以通过一个小的更改来加快速度:将第二个参数传递给 `readdir` AKA 选项对象,就像这样 `readdir(dir, {withFileTypes: true})` 这将返回所有项目及其类型信息,所以我们赢了根本不需要调用 `stat` 来获取 `readdir` 现在返回给我们的信息。这使我们无需进行额外的系统调用。[详情](https://twitter.com/bengl/status/1029196268663259136) (2认同)
  • 是否已知这不能陷入符号链接的无限循环中? (2认同)
  • FWIW,与我测试过的其他一些方法相比,Node 10.10 版本具有*出色的*性能。 (2认同)

Joh*_*sen 86

A.看一下文件模块.它有一个叫做walk的函数:

file.walk(开始,回调)

导航文件树,为每个目录调用回调,传入(null,dirPath,dirs,files).

这可能适合你!是的,它是异步的.但是,如果您需要,我认为您必须自己聚合完整路径.

B.另一种选择,甚至是我最喜欢的一种:使用unix find.为什么要再做一次,已经编程了?也许不完全是你需要的,但仍然值得一试:

var execFile = require('child_process').execFile;
execFile('find', [ 'somepath/' ], function(err, stdout, stderr) {
  var file_list = stdout.split('\n');
  /* now you've got a list with full path file names */
});
Run Code Online (Sandbox Code Playgroud)

Find有一个很好的内置缓存机制,只要只有少数文件夹发生变化,后续搜索速度非常快.

  • 这只是UNIX吗? (8认同)
  • 很好,但不是跨平台. (7认同)

Tho*_*enz 38

另一个不错的npm包是glob.

npm install glob

它非常强大,应该涵盖所有递归需求.

编辑:

我实际上对glob不是很满意,所以我创建了readdirp.

我非常有信心它的API可以递归地查找文件和目录,并且非常容易应用特定的过滤器.

仔细阅读其文档,以便更好地了解它的功能并通过以下方式安装:

npm install readdirp

  • 您能否详细说明创建 readdrip @Thorsten Lorenz 的原因 (4认同)

Mir*_*ili 36

简短、现代且高效:

import {readdir} from 'node:fs/promises'
import {join} from 'node:path'

const walk = async (dirPath) => Promise.all(
  await readdir(dirPath, { withFileTypes: true }).then((entries) => entries.map((entry) => {
    const childPath = join(dirPath, entry.name)
    return entry.isDirectory() ? walk(childPath) : childPath
  })),
)
Run Code Online (Sandbox Code Playgroud)

特别感谢Function的提示:{withFileTypes: true}.


这会自动保留源目录的树结构(您可能需要)。例如如果:

const allFiles = await walk('src')
Run Code Online (Sandbox Code Playgroud)

那么allFiles就会是一棵像这样的

[
  [
    'src/client/api.js',
    'src/client/http-constants.js',
    'src/client/index.html',
    'src/client/index.js',
    [ 'src/client/res/favicon.ico' ],
    'src/client/storage.js'
  ],
  [ 'src/crypto/keygen.js' ],
  'src/discover.js',
  [
    'src/mutations/createNewMutation.js',
    'src/mutations/newAccount.js',
    'src/mutations/transferCredit.js',
    'src/mutations/updateApp.js'
  ],
  [
    'src/server/authentication.js',
    'src/server/handlers.js',
    'src/server/quick-response.js',
    'src/server/server.js',
    'src/server/static-resources.js'
  ],
  [ 'src/util/prompt.js', 'src/util/safeWriteFile.js' ],
  'src/util.js'
]
Run Code Online (Sandbox Code Playgroud)

如果你愿意的话,可以把它压扁:

allFiles.flat(Number.POSITIVE_INFINITY)
Run Code Online (Sandbox Code Playgroud)
[
  'src/client/api.js',
  'src/client/http-constants.js',
  'src/client/index.html',
  'src/client/index.js',
  'src/client/res/favicon.ico',
  'src/client/storage.js',
  'src/crypto/keygen.js',
  'src/discover.js',
  'src/mutations/createNewMutation.js',
  'src/mutations/newAccount.js',
  'src/mutations/transferCredit.js',
  'src/mutations/updateApp.js',
  'src/server/authentication.js',
  'src/server/handlers.js',
  'src/server/quick-response.js',
  'src/server/server.js',
  'src/server/static-resources.js',
  'src/util/prompt.js',
  'src/util/safeWriteFile.js',
  'src/util.js'
]
Run Code Online (Sandbox Code Playgroud)


Dio*_*oso 27

我建议使用node-glob来完成该任务.

var glob = require( 'glob' );  

glob( 'dirname/**/*.js', function( err, files ) {
  console.log( files );
});
Run Code Online (Sandbox Code Playgroud)


Dom*_*nic 14

如果你想使用npm包,扳手是非常好的.

var wrench = require("wrench");

var files = wrench.readdirSyncRecursive("directory");

wrench.readdirRecursive("directory", function (error, files) {
    // live your dreams
});
Run Code Online (Sandbox Code Playgroud)

编辑(2018年):
最近阅读的人:作者在2015年弃用了这个包:

不推荐使用wrench.js,并且在相当长的一段时间内没有更新.我强烈建议使用fs-extra来执行任何额外的文件系统操作.


kal*_*hua 9

我喜欢的答案,从chjj以上,并且不会已经能够没有这种开始创建我的版本并行循环的.

var fs = require("fs");

var tree = function(dir, done) {
  var results = {
        "path": dir
        ,"children": []
      };
  fs.readdir(dir, function(err, list) {
    if (err) { return done(err); }
    var pending = list.length;
    if (!pending) { return done(null, results); }
    list.forEach(function(file) {
      fs.stat(dir + '/' + file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          tree(dir + '/' + file, function(err, res) {
            results.children.push(res);
            if (!--pending){ done(null, results); }
          });
        } else {
          results.children.push({"path": dir + "/" + file});
          if (!--pending) { done(null, results); }
        }
      });
    });
  });
};

module.exports = tree;
Run Code Online (Sandbox Code Playgroud)

我也创造了一个要点.欢迎评论.我仍然在NodeJS领域开始,所以这是我希望了解更多的一种方式.


And*_*dri 9

Vanilla ES6 + 异步/等待 + 小且可读

我在这个帖子中没有找到我想要的答案;不同的答案中有一些相似的元素,但我只是想要一些简单易读的东西。

以防万一它对将来的任何人(即几个月后的我自己)有帮助,这就是我最终使用的:

const { readdir } = require('fs/promises');
const { join } = require('path');

const readdirRecursive = async dir => {
  const files = await readdir( dir, { withFileTypes: true } );

  const paths = files.map( async file => {
    const path = join( dir, file.name );

    if ( file.isDirectory() ) return await readdirRecursive( path );

    return path;
  } );

  return ( await Promise.all( paths ) ).flat( Infinity );
}

module.exports = {
  readdirRecursive,
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*eek 8

使用node-dir准确生成您喜欢的输出

var dir = require('node-dir');

dir.files(__dirname, function(err, files) {
  if (err) throw err;
  console.log(files);
  //we have an array of files now, so now we can iterate that array
  files.forEach(function(path) {
    action(null, path);
  })
});
Run Code Online (Sandbox Code Playgroud)


Got*_*ure 8

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

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
}
Run Code Online (Sandbox Code Playgroud)

用法:

const files = getFiles(process.cwd())

console.log(files)
Run Code Online (Sandbox Code Playgroud)

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


Til*_*ilo 8

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

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();
}
Run Code Online (Sandbox Code Playgroud)


Loo*_*urr 7

随着递归

var fs = require('fs')
var path = process.cwd()
var files = []

var getFiles = function(path, files){
    fs.readdirSync(path).forEach(function(file){
        var subpath = path + '/' + file;
        if(fs.lstatSync(subpath).isDirectory()){
            getFiles(subpath, files);
        } else {
            files.push(path + '/' + file);
        }
    });     
}
Run Code Online (Sandbox Code Playgroud)

调用

getFiles(path, files)
console.log(files) // will log all files in directory
Run Code Online (Sandbox Code Playgroud)

  • 我建议不要使用`/` 连接路径字符串,而是使用`path` 模块:`path.join(searchPath, file)`。这样,您将获得独立于操作系统的正确路径。 (2认同)

Afa*_*kin 7

异步

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)
Run Code Online (Sandbox Code Playgroud)

同步

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))
Run Code Online (Sandbox Code Playgroud)

异步可读

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)
Run Code Online (Sandbox Code Playgroud)

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


Gan*_*der 7

qwtel答案变体,在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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

简单、基于异步 Promise


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
    }
};
Run Code Online (Sandbox Code Playgroud)

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


mbe*_*sky 6

v20.1版本中提供的最短的本机解决方案:

import fs from 'node:fs'

const results = fs.promises.readdir('/tmp', { recursive: true })
Run Code Online (Sandbox Code Playgroud)

recursive选项也受fs.readdirfs.readdirSync函数支持。


mpe*_*pen 5

使用 async/await,这应该可以工作:

const FS = require('fs');
const readDir = promisify(FS.readdir);
const fileStat = promisify(FS.stat);

async function getFiles(dir) {
    let files = await readDir(dir);

    let result = files.map(file => {
        let path = Path.join(dir,file);
        return fileStat(path).then(stat => stat.isDirectory() ? getFiles(path) : path);
    });

    return flatten(await Promise.all(result));
}

function flatten(arr) {
    return Array.prototype.concat(...arr);
}
Run Code Online (Sandbox Code Playgroud)

您可以使用bluebird.Promisify或这个:

/**
 * Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument.
 *
 * @param {Function} nodeFunction
 * @returns {Function}
 */
module.exports = function promisify(nodeFunction) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            nodeFunction.call(this, ...args, (err, data) => {
                if(err) {
                    reject(err);
                } else {
                    resolve(data);
                }
            })
        });
    };
};
Run Code Online (Sandbox Code Playgroud)

Node 8+内置了 Promisify

请参阅我的其他答案,了解可以更快给出结果的生成器方法。