node.js fs.readdir búsqueda de directorio recursivo

¿Alguna idea sobre una búsqueda de directorio asíncrona 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 estoy un poco preocupado de que no sea asincrónico …

¿Algunas ideas? He visto node-walk, que es genial, pero no me da solo los archivos en una matriz, como lo hace readdir. A pesar de que

Buscando resultados como …

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

Básicamente hay dos formas de lograr esto. En un entorno asíncrono, observará que hay dos tipos de bucles: seriales y paralelos. Un bucle en serie espera a que se complete una iteración antes de pasar a la siguiente iteración; esto garantiza que cada iteración del ciclo se complete en orden. En un bucle paralelo, todas las iteraciones se inician al mismo tiempo, y una puede completarse antes que otra, sin embargo, es mucho más rápido que un bucle en serie. Entonces, en este caso, probablemente sea mejor utilizar un bucle paralelo porque no importa en qué orden finalice la caminata, siempre que complete y devuelva los resultados (a menos que los desee 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 de inicio (ADVERTENCIA: la lista de resultados será enorme si tiene muchas cosas en su directorio de inicio):

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

EDITAR: ejemplos mejorados.

A. Eche un vistazo al módulo de archivos . Tiene una función llamada caminar:

file.walk (inicio, callback)

Navega por un árbol de archivos, llamando a la callback para cada directorio, pasando (null, dirPath, dirs, archivos).

Esto puede ser para ti! Y sí, es asincrónico. Sin embargo, creo que tendrías que agregar la ruta completa, si es que la necesitas.

B. Una alternativa, e incluso una de mis favoritas: use el buscador de Unix para eso. ¿Por qué hacer algo otra vez, eso ya ha sido progtwigdo? Tal vez no sea exactamente lo que necesita, 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é que hace que las búsquedas posteriores sean muy rápidas, siempre y cuando solo hayan cambiado algunas carpetas.

En caso de que alguien lo encuentre útil, también armé 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: Para usar menos recursos al filtrar. Filtra dentro de esta función. Por ejemplo, Replace results.push(file); con el siguiente código Ajustar según sea necesario:

  file_type = file.split(".").pop(); file_name = file.split(/(\\|\/)/g).pop(); if (file_type == "json") results.push(file); 

Otro buen paquete npm es glob .

npm install glob

Es muy poderoso y debe cubrir todas sus necesidades recursivas.

Editar:

De hecho, no estaba muy contento con glob, así que creé readdirp .

Estoy muy seguro de que su API hace que encontrar archivos y directorios recursivamente y aplicar filtros específicos sea muy fácil.

Lea su documentación para tener una mejor idea de lo que hace e instalar a través de:

npm install readdirp

Recomiendo usar node-glob para realizar esa tarea.

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

Esta utiliza la cantidad máxima de funciones nuevas y de moda disponibles en el nodo 8, incluidas Promesas, util / promisify, desestructuración, async-await, map + reduce y más, haciendo que sus compañeros de trabajo se rasquen la cabeza mientras intentan descubrir 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 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), []); } 

Uso:

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

Si desea usar un paquete npm, la llave inglesa es bastante buena.

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

EDITAR (2018):
Alguien leyendo en los últimos tiempos: el autor desaprobó este paquete en 2015:

wrench.js está en desuso, y no se ha actualizado en bastante tiempo. Recomiendo utilizar fs-extra para realizar operaciones adicionales del sistema de archivos.

Me encantó la respuesta de chjj anterior y no habría sido capaz de 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; 

Creé un Gist también. Comentarios bienvenidos. Todavía estoy comenzando en el reino de NodeJS, así que esa es una forma en la que espero aprender más.

Usa node-dir para producir exactamente el resultado 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); }) }); 

Con recursión

 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 

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

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

Una biblioteca llamada Filehound es otra opción. Buscará recursivamente un directorio determinado (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 especifica un directorio específico:

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

Consulte los documentos para conocer más casos de uso y ejemplos de uso: https://github.com/nspragg/filehound

Descargo de responsabilidad: soy el autor.

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

Implementación de promesa autónoma

Estoy usando la biblioteca de la promesa when.js 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 .

klaw y klaw-sync son dignos de consideración para este tipo de cosas. Estos fueron parte de node-fs-extra .

Aquí hay otra implementación. Ninguna de las soluciones anteriores tiene limitadores, por lo que si la estructura de su directorio es grande, todas se agotarán 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); } }; 

Usar una concurrencia de 50 funciona bastante bien, y es casi tan rápido como implementaciones simples para estructuras de directorios pequeñas.

El módulo recursive-readdir tiene esta funcionalidad.

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

Para divertirse, aquí hay una versión basada en flujo que funciona con la biblioteca de streams 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)) 

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

Debo agregar la biblioteca de sander basada en Promise a la lista.

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

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

Como todos deberían escribir el suyo, 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); } } }) }); }); } 

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 necesita la extensión también.

Una ventaja adicional es que también mirará los archivos y llamará de nuevo a la callback. Hay muchas opciones de configuración para hacerlo extremadamente flexible.

Solo rehice la gem de guard de ruby ​​usando loaddir en poco tiempo

Esta es mi respuesta. Espero que pueda ayudar a alguien.

Mi objective es hacer que la rutina de búsqueda pueda detenerse en cualquier lugar, y para encontrar un archivo, dice la profundidad relativa a 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. 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); 

Otra simple 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; } } 

Soy reacio a agregar otra respuesta al montón, pero me gustó esta respuesta (la única que usaba async / await), pero pensé que necesitaba un poco de limpieza y simplificación:

 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 } 

Se usa así:

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

Así es como uso la función nodejs fs.readdir para buscar recursivamente 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 tienes una ruta llamada '/ base de datos' en tu nodo proyectos raíz. Una vez que se haya resuelto esta promesa, debería escupir una matriz de cada archivo en '/ base de datos'.

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

Otra respuesta, 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, * eg 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); } } }); }); }); };