node.js - read - nodejs list files in directory recursively
node.js fs.readdir búsqueda de directorio recursiva (30)
¿Alguna idea en una búsqueda de directorio asíncrono usando fs.readdir? Me doy cuenta de que podríamos introducir la recursión y llamar a la función de directorio de lectura con el siguiente directorio para leer, pero me preocupa un poco que no sea asíncrono ...
¿Algunas ideas? He mirado a node-walk cual es genial, pero no me da solo los archivos en una matriz, como lo hace readdir. A pesar de que
Buscando salida como ...
[''file1.txt'', ''file2.txt'', ''dir/file3.txt'']
Implementación de promesa independiente
Estoy usando la biblioteca when.js promise en este ejemplo.
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);
});
He incluido un parámetro opcional includeDir
que incluirá directorios en la lista de archivos si se establece en true
.
Aquí hay otra implementación. Ninguna de las soluciones anteriores tiene ningún limitador, por lo que si la estructura de su directorio es grande, todas se irán a pique y eventualmente se quedarán sin recursos.
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);
}
};
El uso de una concurrencia de 50 funciona bastante bien, y es casi tan rápido como implementaciones más simples para estructuras de directorios pequeños.
Aquí hay un método recursivo para obtener todos los archivos, incluidos los subdirectorios.
const FileSystem = require("fs");
const Path = require("path");
//...
function getFiles(directory) {
directory = Path.normalize(directory);
let files = FileSystem.readdirSync(directory).map((file) => directory + Path.sep + file);
files.forEach((file, index) => {
if (FileSystem.statSync(file).isDirectory()) {
Array.prototype.splice.apply(files, [index, 1].concat(getFiles(file)));
}
});
return files;
}
Así es como uso la función nodejs fs.readdir para buscar recursivamente en un directorio.
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;
};
Digamos que tiene una ruta llamada ''/ database'' en la raíz de su proyecto de nodo. Una vez que se resuelva esta promesa, debe escupir una matriz de cada archivo en ''/ database''.
readdirRecursivePromise(''database'').then(out => {
console.log(flatten(out));
}).catch(err => {
console.log(err);
});
Con 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);
}
});
}
Vocación
getFiles(path, files)
console.log(files) // will log all files in directory
Debo agregar la biblioteca de sander basada en la promesa a la lista.
var sander = require(''sander'');
sander.lsr(directory).then( filenames => { console.log(filenames) } );
Echa un vistazo a la biblioteca final-fs . Proporciona una función 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
});
El módulo recursive-readdir tiene esta funcionalidad.
Esta es mi respuesta. Espero que pueda ayudar a alguien.
Mi objetivo es hacer que la rutina de búsqueda se detenga en cualquier lugar y, para un archivo encontrado, indica la profundidad relativa de la ruta original.
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);
Esta utiliza la cantidad máxima de nuevas funciones de moda disponibles en el nodo 8, que incluyen Promesas, util / promisify, desestructuración, async-await, map + reduce y más, lo que hace que sus compañeros de trabajo se rascen la cabeza mientras tratan de averiguar qué está pasando.
Sin dependencias externas (nodo 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), []);
}
Uso:
getFiles(__dirname)
.then(files => console.log(files))
.catch(e => console.error(e));
Hay básicamente dos formas de lograr esto. En un entorno asíncrono, notará que hay dos tipos de bucles: en serie y en paralelo. Un bucle en serie espera a que se complete una iteración antes de pasar a la siguiente iteración, lo que garantiza que cada iteración del bucle se complete en orden. En un bucle paralelo, todas las iteraciones se inician al mismo tiempo, y una puede completarse antes de la otra, sin embargo, es mucho más rápida que un bucle en serie. Entonces, en este caso, probablemente sea mejor usar un bucle paralelo porque no importa en qué orden se complete la caminata, siempre y cuando complete y devuelva los resultados (a menos que los quiera en orden).
Un bucle paralelo se vería así:
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);
}
});
});
});
};
Un bucle en serie se vería así:
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();
}
});
})();
});
};
Y para probarlo en su directorio personal (ADVERTENCIA: la lista de resultados será enorme si tiene muchas cosas en su directorio principal):
walk(process.env.HOME, function(err, results) {
if (err) throw err;
console.log(results);
});
EDIT: ejemplos mejorados.
He codificado esto recientemente, y pensé que tendría sentido compartir esto aquí. El código hace uso de la biblioteca asíncrona .
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);
});
});
};
Puedes usarlo así:
scan(''/some/dir'', ''.ext'', function(err, files) {
// Do something with files that ends in ''.ext''.
console.log(files);
});
Me encantó la respuesta del anterior y no habría podido crear mi versión del bucle paralelo sin ese inicio.
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;
También creé una Gist . Comentarios bienvenidos. Todavía estoy empezando en el reino de NodeJS, así que es una forma en la que espero aprender más.
Modifiqué la respuesta basada en la Promesa de Trevor Senior para trabajar con 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); {
});
Otra respuesta más, pero esta vez usando 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);
}
}
});
});
});
};
Otra sencilla y útil.
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;
}
}
Otro buen paquete de npm es glob .
npm install glob
Es muy potente y debe cubrir todas sus necesidades recurrentes.
Editar:
En realidad no estaba perfectamente feliz con glob, así que creé readdirp .
Estoy seguro de que su API hace que la búsqueda de archivos y directorios recursivamente y la aplicación de filtros específicos sea muy fácil.
Lea su documentation para tener una mejor idea de lo que hace e instale a través de:
npm install readdirp
Para divertirse, aquí hay una versión basada en flujo que funciona con la biblioteca de flujos highland.js. Fue co-escrito por Victor 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))
Por si alguien lo encuentra útil, también armo una versión síncrona .
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;
}
Consejo: Usar menos recursos al filtrar. Filtrar dentro de esta misma función. Por ejemplo, Reemplace results.push(file);
con el siguiente código. Ajuste según sea necesario:
file_type = file.split(".").pop();
file_name = file.split(/(//|//)/g).pop();
if (file_type == "json") results.push(file);
Porque cada uno debería escribir el suyo, yo hice uno.
walk (dir, cb, endCb) cb (archivo) endCb (err | null)
SUCIO
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);
}
}
})
});
});
}
Recomiendo usar glob para lograr esa tarea.
var glob = require( ''glob'' );
glob( ''dirname/**/*.js'', function( err, files ) {
console.log( files );
});
Si quieres usar un paquete npm, Wrench es bastante bueno.
var wrench = require("wrench");
var files = wrench.readdirSyncRecursive("directory");
wrench.readdirRecursive("directory", function (error, files) {
// live your dreams
});
EDITAR (2018):
Cualquiera que haya leído recientemente: el autor desaprobó este paquete en 2015:
wrench.js está en desuso y no se ha actualizado en bastante tiempo. Recomiendo encarecidamente usar fs-extra para realizar cualquier operación adicional del sistema de archivos.
Una biblioteca llamada Filehound es otra opción. Buscará recursivamente un directorio dado (directorio de trabajo por defecto). Es compatible con varios filtros, devoluciones de llamada, promesas y búsquedas de sincronización.
Por ejemplo, busque en el directorio de trabajo actual todos los archivos (usando devoluciones de llamada):
const Filehound = require(''filehound'');
Filehound.create()
.find((err, files) => {
if (err) {
return console.error(`error: ${err}`);
}
console.log(files); // array of files
});
O promete y especificando un directorio específico:
const Filehound = require(''filehound'');
Filehound.create()
.paths("/tmp")
.find()
.each(console.log);
Consulte la documentación para más casos de uso y ejemplos de uso: https://github.com/nspragg/filehound
Descargo de responsabilidad: Soy el autor.
Usa node-dir para producir exactamente la salida que te gusta
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);
})
});
Usando Promesas ( Q ) para resolver esto en un estilo funcional:
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);
});
});
};
Devuelve una promesa de una matriz, por lo que puede usarla como:
walk(''/home/mypath'').then(function (files) { console.log(files); });
Usando async / await, esto debería funcionar:
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);
}
Puedes usar bluebird.Promisify o esto:
/**
* 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);
}
})
});
};
};
Utilizando bluebird 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));
echa un vistazo a 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); }
})
Puede usar fileName
lugar de baseName
si también necesita la extensión.
Una ventaja adicional es que también verá los archivos y volverá a llamar a la devolución de llamada. Hay toneladas de opciones de configuración para que sea extremadamente flexible.
Acabo de rehacer la gema guard
de ruby usando loaddir en poco tiempo
klaw pena considerar klaw y klaw para este tipo de cosas. Estos fueron parte de node-fs-extra .
A. Echa un vistazo al módulo de archivo . Tiene una función llamada caminata:
file.walk (inicio, devolución de llamada)
Navega por un árbol de archivos, llama a la devolución de llamada para cada directorio y pasa (null, dirPath, dirs, files).
Esto puede ser para ti! Y sí, es asíncrono. Sin embargo, creo que tendrías que agregar el camino completo tú mismo, si los necesitaras.
B. Una alternativa, e incluso una de mis favoritas: usar la find
Unix para eso. ¿Por qué hacer algo de nuevo, que ya ha sido programado? Tal vez no exactamente lo que necesitas, pero vale la pena echarle un vistazo:
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 tiene un buen mecanismo de almacenamiento en caché incorporado que hace que las búsquedas posteriores sean muy rápidas, siempre y cuando solo algunas carpetas hayan cambiado.