node.js fs.readdirrecursion目录search

有关使用fs.readdir进行asynchronous目录search的任何想法? 我意识到我们可以引入recursion,并调用读取目录函数与下一个目录阅读,但有点担心它不是asynchronous…

有任何想法吗? 我已经看过node-walk ,这很棒,但是不会像readdir那样给我一个数组中的文件。 虽然

寻找输出像…

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

基本上有两种方法来完成这一点。 在asynchronous环境中,您会注意到有两种循环:串行和并行。 串行循环在进入下一次迭代之前等待一个迭代完成 – 这保证循环的每次迭代都按顺序完成。 在一个并行循环中,所有的迭代都是在同一时间开始的,而且可以在另一个之前完成,但是它比串行循环要快得多。 所以在这种情况下,使用一个并行循环可能会更好,因为只要它完成并返回结果(除非您希望它们按顺序排列),那么执行完成的顺序无关紧要。

一个并行循环看起来像这样:

 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); } }); }); }); }; 

串行循环如下所示:

 var fs = require('fs'); 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 = 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(); } }); })(); }); }; 

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

 walk(process.env.HOME, function(err, results) { if (err) throw err; console.log(results); }); 

编辑:改进的例子。

A.看看文件模块 。 它有一个叫做walk的function:

file.walk(开始,callback)

浏览文件树,调用每个目录的callback,传入(null,dirPath,dirs,files)。

这可能适合你! 是的,这是asynchronous的。 不过,如果你需要的话,我想你必须自己整理完整path。

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 */ }); 

查找有一个很好的内置caching机制,使后续的search速度非常快,只要只有less数文件夹已经改变。

以防万一有人发现它有用,我也把一个同步版本。

 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()) results = results.concat(walk(file)) else results.push(file) }) return results } 

另一个不错的npm包是glob 。

npm install glob

这是非常强大的,应该涵盖所有的recursion需求。

编辑:

我实际上对glob并不满意,所以我创build了readdirp 。

我非常有信心,它的API可以recursion地查找文件和目录,并且非常容易地应用特定的filter。

阅读它的文档 ,以更好地了解它的function和安装方法:

npm install readdirp

我推荐使用node-glob来完成这个任务。

 var glob = require( 'glob' ); glob( 'dirname/**/*.js', function( err, files ) { console.log( files ); }); 

如果你想使用npm包, 扳手是相当不错的。

 var wrench = require("wrench"); var files = wrench.readdirSyncRecursive("directory"); wrench.readdirRecursive("directory", function (error, files) { // live your dreams }); 

我喜欢从上面的chjj 的答案 ,并没有能够创build我的版本的并行循环没有开始。

 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; 

我也创build了一个Gist 。 评论欢迎。 我仍然在NodeJS领域开始,所以这是我希望了解更多的方法。

使用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); }) }); 

这个使用了节点8中可用的最新的stream行function,包括Promises,util / promisify,解构,asynchronous等待,map + reduce等等,让你的同事在试图找出什么正在进行。

没有外部依赖(节点8+)。

 const { promisify } = require('util'); const { resolve } = require('path'); const fs = require('fs'); const readdir = promisify(fs.readdir); const rename = promisify(fs.rename); 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), []); } 

用法:

 getFiles(__dirname) .then(files => console.log(files)) .catch(e => console.error(e)); 

recursion

 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); } }); } 

调用

 getFiles(path, files) console.log(files) // will log all files in directory 

我最近编写了这个代码,认为在这里分享这个是有道理的。 该代码使用asynchronous库 。

 var fs = require('fs'); var async = require('async'); var scan = function(dir, suffix, callback) { fs.readdir(dir, function(err, files) { var returnFiles = []; async.each(files, function(file, next) { var filePath = dir + '/' + file; fs.stat(filePath, function(err, stat) { if (err) { return next(err); } if (stat.isDirectory()) { scan(filePath, suffix, function(err, results) { if (err) { return next(err); } returnFiles = returnFiles.concat(results); next(); }) } else if (stat.isFile()) { if (file.indexOf(suffix, file.length - suffix.length) !== -1) { returnFiles.push(filePath); } next(); } }); }, function(err) { callback(err, returnFiles); }); }); }; 

你可以像这样使用它:

 scan('/some/dir', '.ext', function(err, files) { // Do something with files that ends in '.ext'. console.log(files); }); 

看看最后的fs库。 它提供了一个readdirRecursive函数:

 ffs.readdirRecursive(dirPath, true, 'my/initial/path') .then(function (files) { // in the `files` variable you've got all the files }) .otherwise(function (err) { // something went wrong }); 

一个名为Filehound的库是另一种select。 它将recursionsearch给定的目录(默认情况下,工作目录)。 它支持各种filter,callback,承诺和同步search。

例如,search当前工作目录中的所有文件(使用callback):

 const Filehound = require('filehound'); Filehound.create() .find((err, files) => { if (err) { return console.error(`error: ${err}`); } console.log(files); // array of files }); 

或者承诺并指定一个特定的目录:

 const Filehound = require('filehound'); Filehound.create() .paths("/tmp") .find() .each(console.log); 

请参阅文档以获取更多使用案例和使用示例: https : //github.com/nspragg/filehound

免责声明:我是作者。

使用asynchronous/等待,这应该工作:

 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); } 

你可以使用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); } }) }); }; }; 

独立承诺实施

我在这个例子中使用了when.js承诺库。

 var fs = require('fs') , path = require('path') , when = require('when') , nodefn = require('when/node/function'); function walk (directory, includeDir) { var results = []; return when.map(nodefn.call(fs.readdir, directory), function(file) { file = path.join(directory, file); return nodefn.call(fs.stat, file).then(function(stat) { if (stat.isFile()) { return results.push(file); } if (includeDir) { results.push(file + path.sep); } return walk(file, includeDir).then(function(filesInDir) { results = results.concat(filesInDir); }); }); }).then(function() { return results; }); }; walk(__dirname).then(function(files) { console.log(files); }).otherwise(function(error) { console.error(error.stack || error); }); 

我已经包含了一个可选参数includeDir ,如果设置为true ,它将包含文件列表中的目录。

这是另一个实现。 上述解决scheme都没有任何限制,所以如果你的目录结构很大,它们都会瘫痪,并最终耗尽资源。

 var async = require('async'); var fs = require('fs'); var resolve = require('path').resolve; var scan = function(path, concurrency, callback) { var list = []; var walker = async.queue(function(path, callback) { fs.stat(path, function(err, stats) { if (err) { return callback(err); } else { if (stats.isDirectory()) { fs.readdir(path, function(err, files) { if (err) { callback(err); } else { for (var i = 0; i < files.length; i++) { walker.push(resolve(path, files[i])); } callback(); } }); } else { list.push(path); callback(); } } }); }, concurrency); walker.push(path); walker.drain = function() { callback(list); } }; 

使用50个作品的并发性相当好,几乎和小目录结构的简单实现一样快。

recursionreaddir模块具有此function。

我修改了Trevor Senior的Promise基础,与Bluebird合作

 var fs = require('fs'), path = require('path'), Promise = require('bluebird'); var readdirAsync = Promise.promisify(fs.readdir); var statAsync = Promise.promisify(fs.stat); function walkFiles (directory) { var results = []; return readdirAsync(directory).map(function(file) { file = path.join(directory, file); return statAsync(file).then(function(stat) { if (stat.isFile()) { return results.push(file); } return walkFiles(file).then(function(filesInDir) { results = results.concat(filesInDir); }); }); }).then(function() { return results; }); } //use walkDir(__dirname).then(function(files) { console.log(files); }).catch(function(e) { console.error(e); { }); 

为了好玩,这里是一个基于stream的版本,可以和highland.jsstream库一起工作。 这是维克托·Vu合着的。

 ### directory >---m------> dirFilesStream >---------o----> out | | | | +--------< returnPipe <-----------+ legend: (m)erge (o)bserve + directory has the initial file + dirListStream does a directory listing + out prints out the full path of the file + returnPipe runs stat and filters on directories ### _ = require('highland') fs = require('fs') fsPath = require('path') directory = _(['someDirectory']) mergePoint = _() dirFilesStream = mergePoint.merge().flatMap((parentPath) -> _.wrapCallback(fs.readdir)(parentPath).sequence().map (path) -> fsPath.join parentPath, path ) out = dirFilesStream # Create the return pipe returnPipe = dirFilesStream.observe().flatFilter((path) -> _.wrapCallback(fs.stat)(path).map (v) -> v.isDirectory() ) # Connect up the merge point now that we have all of our streams. mergePoint.write directory mergePoint.write returnPipe mergePoint.end() # Release backpressure. This will print files as they are discovered out.each H.log # Another way would be to queue them all up and then print them all out at once. # out.toArray((files)-> console.log(files)) 

使用Promises( Q )来解决这个function风格:

 var fs = require('fs'), fsPath = require('path'), Q = require('q'); var walk = function (dir) { return Q.ninvoke(fs, 'readdir', dir).then(function (files) { return Q.all(files.map(function (file) { file = fsPath.join(dir, file); return Q.ninvoke(fs, 'lstat', file).then(function (stat) { if (stat.isDirectory()) { return walk(file); } else { return [file]; } }); })); }).then(function (files) { return files.reduce(function (pre, cur) { return pre.concat(cur); }); }); }; 

它返回一个数组的承诺,所以你可以使用它:

 walk('/home/mypath').then(function (files) { console.log(files); }); 

我必须将基于Promise的打磨机库添加到列表中。

  var sander = require('sander'); sander.lsr(directory).then( filenames => { console.log(filenames) } ); 

使用蓝鸟promise.coroutine:

 let promise = require('bluebird'), PC = promise.coroutine, fs = promise.promisifyAll(require('fs')); let getFiles = PC(function*(dir){ let files = []; let contents = yield fs.readdirAsync(dir); for (let i = 0, l = contents.length; i < l; i ++) { //to remove dot(hidden) files on MAC if (/^\..*/.test(contents[i])) contents.splice(i, 1); } for (let i = 0, l = contents.length; i < l; i ++) { let content = path.resolve(dir, contents[i]); let contentStat = yield fs.statAsync(content); if (contentStat && contentStat.isDirectory()) { let subFiles = yield getFiles(content); files = files.concat(subFiles); } else { files.push(content); } } return files; }); //how to use //easy error handling in one place getFiles(your_dir).then(console.log).catch(err => console.log(err)); 

因为每个人都应该自己写,所以我做了一个。

walk(dir,cb,endCb)cb(file)endCb(err | null)

 module.exports = walk; function walk(dir, cb, endCb) { var fs = require('fs'); var path = require('path'); fs.readdir(dir, function(err, files) { if (err) { return endCb(err); } var pending = files.length; if (pending === 0) { endCb(null); } files.forEach(function(file) { fs.stat(path.join(dir, file), function(err, stats) { if (err) { return endCb(err) } if (stats.isDirectory()) { walk(path.join(dir, file), cb, function() { pending--; if (pending === 0) { endCb(null); } }); } else { cb(path.join(dir, file)); pending--; if (pending === 0) { endCb(null); } } }) }); }); } 

请查看loaddir https://npmjs.org/package/loaddir

npm install loaddir

  loaddir = require('loaddir') allJavascripts = [] loaddir({ path: __dirname + '/public/javascripts', callback: function(){ allJavascripts.push(this.relativePath + this.baseName); } }) 

如果您还需要扩展名,则可以使用fileName而不是baseName

另外一个好处就是它也会监视这些文件,并再次调用callback函数。 有大量的configuration选项使其非常灵活。

我只是在短时间内使用loaddir从ruby重新创build了guardgem

这是我的答案。 希望它能帮助别人。

我的重点是使search程序可以在任何地方停下来,并find一个文件,告诉相对深度的原始path。

 var _fs = require('fs'); var _path = require('path'); var _defer = process.nextTick; // next() will pop the first element from an array and return it, together with // the recursive depth and the container array of the element. ie If the first // element is an array, it'll be dug into recursively. But if the first element is // an empty array, it'll be simply popped and ignored. // eg If the original array is [1,[2],3], next() will return [1,0,[[2],3]], and // the array becomes [[2],3]. If the array is [[[],[1,2],3],4], next() will return // [1,2,[2]], and the array becomes [[[2],3],4]. // There is an infinity loop `while(true) {...}`, because I optimized the code to // make it a non-recursive version. var next = function(c) { var a = c; var n = 0; while (true) { if (a.length == 0) return null; var x = a[0]; if (x.constructor == Array) { if (x.length > 0) { a = x; ++n; } else { a.shift(); a = c; n = 0; } } else { a.shift(); return [x, n, a]; } } } // cb is the callback function, it have four arguments: // 1) an error object if any exception happens; // 2) a path name, may be a directory or a file; // 3) a flag, `true` means directory, and `false` means file; // 4) a zero-based number indicates the depth relative to the original path. // cb should return a state value to tell whether the searching routine should // continue: `true` means it should continue; `false` means it should stop here; // but for a directory, there is a third state `null`, means it should do not // dig into the directory and continue searching the next file. var ls = function(path, cb) { // use `_path.resolve()` to correctly handle '.' and '..'. var c = [ _path.resolve(path) ]; var f = function() { var p = next(c); p && s(p); }; var s = function(p) { _fs.stat(p[0], function(err, ss) { if (err) { // use `_defer()` to turn a recursive call into a non-recursive call. cb(err, p[0], null, p[1]) && _defer(f); } else if (ss.isDirectory()) { var y = cb(null, p[0], true, p[1]); if (y) r(p); else if (y == null) _defer(f); } else { cb(null, p[0], false, p[1]) && _defer(f); } }); }; var r = function(p) { _fs.readdir(p[0], function(err, files) { if (err) { cb(err, p[0], true, p[1]) && _defer(f); } else { // not use `Array.prototype.map()` because we can make each change on site. for (var i = 0; i < files.length; i++) { files[i] = _path.join(p[0], files[i]); } p[2].unshift(files); _defer(f); } }); } _defer(f); }; var printfile = function(err, file, isdir, n) { if (err) { console.log('--> ' + ('[' + n + '] ') + file + ': ' + err); return true; } else { console.log('... ' + ('[' + n + '] ') + (isdir ? 'D' : 'F') + ' ' + file); return true; } }; var path = process.argv[2]; ls(path, printfile); 

klaw和klaw-sync是值得考虑的这类事情。 这些是node-fs-extra的一部分 。

另一个简单而有用的

 function walkDir(root) { const stat = fs.statSync(root); if (stat.isDirectory()) { const dirs = fs.readdirSync(root).filter(item => !item.startsWith('.')); let results = dirs.map(sub => walkDir(`${root}/${sub}`)); return [].concat(...results); } else { return root; } } 

我不愿意为这个堆添加另一个答案,但是我喜欢这个答案 (唯一一个使用asynchronous/等待的答案 ),但是认为它需要一些整理和简化:

 async function getFileDescendents(dir) { const fs = require('fs-promise'), path = require('path'); let files = await fs.readdir(dir); let result = files.map(file => { let p = path.join(dir,file); return fs.stat(p).then(stat => stat.isDirectory() ? getFileDescendents(p) : p); }); return Array.prototype.concat(...(await Promise.all(result))); // flatten } 

它是这样使用的:

 let files = await getFileDescendents('./my_directory'); 

这是我如何使用nodejs fs.readdir函数recursionsearch一个目录。

 const fs = require('fs'); const mime = require('mime-types'); const readdirRecursivePromise = path => { return new Promise((resolve, reject) => { fs.readdir(path, (err, directoriesPaths) => { if (err) { reject(err); } else { if (directoriesPaths.indexOf('.DS_Store') != -1) { directoriesPaths.splice(directoriesPaths.indexOf('.DS_Store'), 1); } directoriesPaths.forEach((e, i) => { directoriesPaths[i] = statPromise(`${path}/${e}`); }); Promise.all(directoriesPaths).then(out => { resolve(out); }).catch(err => { reject(err); }); } }); }); }; const statPromise = path => { return new Promise((resolve, reject) => { fs.stat(path, (err, stats) => { if (err) { reject(err); } else { if (stats.isDirectory()) { readdirRecursivePromise(path).then(out => { resolve(out); }).catch(err => { reject(err); }); } else if (stats.isFile()) { resolve({ 'path': path, 'type': mime.lookup(path) }); } else { reject(`Error parsing path: ${path}`); } } }); }); }; const flatten = (arr, result = []) => { for (let i = 0, length = arr.length; i < length; i++) { const value = arr[i]; if (Array.isArray(value)) { flatten(value, result); } else { result.push(value); } } return result; }; 

假设您的节点项目根目录中有一个名为“/ database”的path。 一旦这个承诺解决了,它应该在'/数据库'下的每个文件的数组。

 readdirRecursivePromise('database').then(out => { console.log(flatten(out)); }).catch(err => { console.log(err); }); 

这里是完整的工作代码。 根据您的要求。 您可以recursion获取所有文件和文件夹。

 var recur = function(dir) { fs.readdir(dir,function(err,list){ list.forEach(function(file){ var file2 = path.resolve(dir, file); fs.stat(file2,function(err,stats){ if(stats.isDirectory()) { recur(file2); } else { console.log(file2); } }) }) }); }; recur(path); 

在path中给出你想要search的目录path,如“c:\ test”