узел.js fs.рекурсивный поиск каталогов readdir
любые идеи по поиску асинхронного каталога с помощью fs.вызове readdir? Я понимаю, что мы могли бы ввести рекурсию и вызвать функцию read directory со следующим каталогом для чтения, но я немного беспокоюсь о том, что это не асинхронно...
какие идеи? Я смотрел на node-walk что здорово, но не дает мне только файлы в массиве, как это делает readdir. Хотя
Поиск вывода like...
['file1.txt', 'file2.txt', 'dir/file3.txt']
30 ответов
есть в основном два способа достижения этого. В асинхронной среде вы заметите, что есть два вида циклов: последовательный и параллельный. Последовательный цикл ожидает завершения одной итерации, прежде чем перейти к следующей итерации - это гарантирует, что каждая итерация цикла завершается по порядку. В параллельном цикле все итерации запускаются одновременно, и одна может завершиться раньше другой, однако это намного быстрее, чем последовательный цикл. Так что в данном случае это вероятно, лучше использовать параллельный цикл, потому что не имеет значения, в каком порядке завершается прогулка, до тех пор, пока она завершается и возвращает результаты (если вы не хотите, чтобы они были в порядке).
параллельный цикл будет выглядеть так:
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();
}
});
})();
});
};
и проверить его на вашем домашнем каталоге (предупреждение: список результатов будет огромным, если у вас есть много вещей в вашем домашнем каталоге):
walk(process.env.HOME, function(err, results) {
if (err) throw err;
console.log(results);
});
изменить: Улучшенные примеры.
А. посмотреть модуль. Он имеет функцию walk:
.walk (старт, обратный вызов)
перемещает дерево файлов, вызывая обратный вызов для каждого каталога, передавая (null, dirPath, dirs, files).
Это может быть для вас! И да, это асинхронно. Тем не менее, я думаю, вам придется собрать полный путь самостоятельно, если вам нужно их.
Б. альтернатива и даже одна из моих любимых: используйте 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 */
});
Find имеет хороший встроенный механизм кэширования, который делает последующие поиски очень быстрыми, если только несколько папок изменились.
на всякий случай, если кто-то найдет это полезным, я также собрал синхронно версия.
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;
}
совет: использовать меньше ресурсов при фильтрации. Фильтр внутри самой этой функции. Е. Г. Заменить results.push(file);
С ниже код. Отрегулируйте по мере необходимости:
file_type = file.split(".").pop();
file_name = file.split(/(\|\/)/g).pop();
if (file_type == "json") results.push(file);
еще один хороший пакет npm-это Глоб.
npm install glob
Он очень мощный и должен охватывать все ваши рекурсивные потребности.
Edit:
Я на самом деле не был совершенно доволен glob, поэтому я создал readdirp.
Я очень уверен, что его API делает поиск файлов и каталогов рекурсивно и применение определенных фильтров очень простым.
читать через документация в получите лучшее представление о том, что он делает и устанавливает через:
npm install readdirp
Я рекомендую использовать узел-Глоб для выполнения этой задачи.
var glob = require( 'glob' );
glob( 'dirname/**/*.js', function( err, files ) {
console.log( files );
});
этот использует максимальное количество новых функций buzzwordy, доступных в узле 8, включая обещания, 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), []);
}
использование:
getFiles(__dirname)
.then(files => console.log(files))
.catch(e => console.error(e));
Если вы хотите использовать пакет npm,гаечный ключ - Это очень хорошо.
var wrench = require("wrench");
var files = wrench.readdirSyncRecursive("directory");
wrench.readdirRecursive("directory", function (error, files) {
// live your dreams
});
изменить (2018):
любой, кто читает в последнее время: автор устарел этот пакет в 2015 году:
ключ.js устарел и не обновлялся в течение довольно долгого времени. я настоятельно рекомендую использовать fs-extra для выполнения дополнительных операций с файловой системой.
Мне очень понравилось ответ С 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;
Я создал суть как хорошо. Комментарии приветствуются. Я все еще начинаю в области 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);
})
});
С Рекурсией
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
Я недавно закодировал это и подумал, что имеет смысл поделиться этим здесь. Код использует асинхронный библиотека.
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);
});
Проверьте final-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 другой вариант. Это будет рекурсивно искать данный каталог (рабочий каталог по умолчанию). Он поддерживает различные фильтры, обратные вызовы, обещания и поиск синхронизации.
например, найдите в текущем рабочем каталоге все файлы (используя обратные вызовы):
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
отказ от ответственности: я автор.
используя 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);
}
можно использовать Синяя птица.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);
}
})
});
};
};
автономная реализация обещания
я использую когда.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
.
klaw и klaw-sync стоит рассмотреть для такого рода вещей. Эти были частью node-fs-extra.
здесь еще один реализация. Ни одно из вышеперечисленных решений не имеет ограничений, и поэтому, если ваша структура каталогов велика, все они будут работать и в конечном итоге исчерпают ресурсы.
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 работает довольно хорошо и почти так же быстро, как более простые реализации для небольших структур каталогов.
я модифицировал Тревора старшего обещание основанный ответ для работы с птица
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); {
});
для удовольствия, вот версия на основе потока, которая работает с highland.библиотека потоков js. В соавторстве с Виктором вю.
###
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))
С Помощью Обещаний (Q) чтобы решить эту проблему в функциональном стиле:
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); });
Я должен добавить обещание на основе шлифовальная машина библиотека в список.
var sander = require('sander');
sander.lsr(directory).then( filenames => { console.log(filenames) } );
используя обещание bluebird.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) КБ(файл) 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
Если вам нужно, а также расширение.
дополнительный бонус заключается в том, что он будет смотреть файлы, а также и вызвать обратный вызов снова. Есть тонны вариантов настройки, чтобы сделать его чрезвычайно гибким.
Я просто переделал guard
gem от Рубина с помощью loaddir в короткое время
вот мой ответ. Надеюсь, это может кому-то помочь.
Я сосредоточен на том, чтобы процедура поиска могла остановиться в любом месте, и для найденного файла указывает относительную глубину исходного пути.
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. i.e. 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.
// e.g. 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);
еще один простой и полезный
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;
}
}
Я не хочу добавлять еще один ответ в кучу, но мне понравилось ответ (единственный, который использовал async / await), но подумал, что ему нужно немного прибраться и упростить:
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 для рекурсивного поиска каталога.
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 " в корне проекта узла. Как только это обещание будет разрешено, оно должно выплюнуть массив каждого файла под "/database".
readdirRecursivePromise('database').then(out => {
console.log(flatten(out));
}).catch(err => {
console.log(err);
});
еще один ответ, но на этот раз с помощью 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);
}
}
});
});
});
};