node.js fs.readdir búsqueda recursiva de directorios

268

¿Alguna idea sobre una búsqueda de directorio asíncrono usando fs.readdir? Me doy cuenta de que podríamos introducir la recursividad 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 asíncrono ...

¿Algunas ideas? He mirado el recorrido de nodo 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']
crawf
fuente

Respuestas:

379

Básicamente hay dos formas de lograr esto. En un entorno asíncrono, notará que hay dos tipos de bucles: serie y paralelo. 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 bucle 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 usar un bucle paralelo porque no importa en qué orden se complete la caminata, siempre que se 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 path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};

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.

chjj
fuente
10
Cuidado, la respuesta de "bucle paralelo" de chjj anterior tiene un error en los casos en que se camina una carpeta vacía. La solución es: var pendiente = list.length; if (! pendiente) hecho (nulo, resultados); // ¡agrega esta línea! list.forEach (function (file) {...
Vasil Daskalopoulos
27
file = dir + '/' + file;Esto no es recomendable. Deberías usar: var path = require('path'); file = path.resolve(dir, file);
Leiko
77
@onetrickpony porque si lo path.resolve(...)usas obtendrás una ruta adecuada ya sea que estés en Windows o Unix :) Lo que significa que obtendrás algo así como C:\\some\\foo\\pathen Windows y /some/foo/pathen sistemas Unix
Leiko
19
Voté en contra porque tu respuesta fue excelente cuando la escribiste por primera vez en 2011, pero en 2014 las personas usan módulos de código abierto y escriben menos código y contribuyen a los módulos de los que dependen ellos y muchas otras personas. Por ejemplo, intente node-dir para obtener exactamente la salida requerida por @crawf usando esta línea de código:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek
55
Para cualquier persona confundida acerca de la !--sintaxis, se ha formulado una pregunta al respecto
Tas
147

Este utiliza la cantidad máxima de características nuevas y de moda disponibles en el nodo 8, incluidas Promesas, util / promisify, destructuring, async-wait, map + reduce y más, haciendo que sus compañeros de trabajo se rasquen la cabeza mientras intentan averiguar qué está pasando.

Nodo 8+

Sin dependencias externas.

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

Nodo 10.10+

Actualizado para el nodo 10+ con aún más whizbang:

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

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

Tenga en cuenta que a partir del nodo 11.15.0 puede usarlo en files.flat()lugar de Array.prototype.concat(...files)aplanar la matriz de archivos.

Nodo 11+

Si quieres volar la cabeza de todos por completo, puedes usar la siguiente versión usando iteradores asíncronos . Además de ser realmente genial, también permite a los consumidores obtener resultados uno por uno, lo que lo hace más adecuado para directorios realmente grandes.

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

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

El uso ha cambiado porque el tipo de retorno ahora es un iterador asíncrono en lugar de una promesa

(async () => {
  for await (const f of getFiles('.')) {
    console.log(f);
  }
})()

En caso de que alguien esté interesado, he escrito más sobre iteradores asíncronos aquí: https://qwtel.com/posts/software/async-generators-in-the-wild/

qwtel
fuente
55
La denominación de subdiry subdirses engañosa, ya que pueden ser realmente archivos (sugiero algo como itemInDiro item_in_dirsimplemente en su itemlugar), pero esta solución se siente más limpia que la aceptada y es mucho menos código. Tampoco lo encuentro mucho más complicado que el código en la respuesta aceptada. +1
Zelphir Kaltstahl
1
Podrías hacer que esto sea aún más divertido usando require(fs).promisesy simplemente soltando por util.promisifycompleto. Personalmente alias fs a fs.promises.
MushinNoShin
2
Podemos hacer esto más rápido con un pequeño cambio: pasar el segundo argumento a readdirAKA como el objeto de opciones para que readdir(dir, {withFileTypes: true})esto devuelva todos los elementos con su información de tipo, así que no necesitaremos llamar statpara obtener la información que readdirahora nos da espalda. Esto nos ahorra la necesidad de hacer llamadas sys adicionales. Detalles aquí
cacoder
1
@cacoder Actualizado para incluir withFileTypes. Gracias por el consejo.
qwtel
en el nodo 10.10+, si reemplaza return Array.prototype.concat(...files);con let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));puede asegurarse de que los directorios devuelvan un "/" y no un "\". Si no te importa la expresión regular, también puedes hacerloreturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro el
106

En caso de que alguien lo encuentre útil, también armé una versión sincrónica .

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. Filtrar dentro de esta función misma. Por ejemplo, reemplazar 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);
Victor Powell
fuente
6060
¡Me gusta esta solución, excepto por su falta de punto y coma!
mpen
Esto es simple. Pero también un poco ingenuo. Podría causar un desbordamiento de pila si un directorio contiene un enlace a un directorio padre. Tal vez usar lstaten su lugar? O bien, agregue una verificación de recursividad para limitar el nivel de recursividad.
conradkleinespel
14
Considere utilizar file = require ("ruta").
Join
16
@mpen Semicolones son redundantes
Ally
Esto también funciona mejor para mí. Aunque también agregué un filtro para filtrar por una extensión de archivo específica.
Brian
87

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

file.walk (inicio, devolución de llamada)

Navega por un árbol de archivos, llamando a la devolución de llamada para cada directorio, pasando (nulo, dirPath, dirs, archivos).

¡Esto puede ser para ti! Y sí, es asíncrono. Sin embargo, creo que tendría que agregar el camino completo usted mismo, si los necesitara.

B. Una alternativa, e incluso una de mis favoritas: use el Unix findpara eso. ¿Por qué hacer algo de nuevo, que ya ha sido programado? 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é integrado que hace que las búsquedas posteriores sean muy rápidas, siempre que solo hayan cambiado algunas carpetas.

Johann Philipp Strathausen
fuente
9
¿Es esto solo UNIX?
Mohsen
Tenía una pregunta sobre el ejemplo B: Para execFile () (y exec ()) el stderr y el stdout son Buffers ... entonces, ¿no necesitaría hacer stdout.toString.split ("\ n") ya que los Buffers no son Strings?
Cheruvim
8
agradable, pero no multiplataforma.
f0ster
Por cierto: ¡No, A no es solo Unix! Solo B es solo Unix. Sin embargo, Windows 10 ahora viene con un subsistema Linux. Entonces, incluso B solo funcionaría en Windows hoy en día.
Johann Philipp Strathausen
¿No debería habilitarse WSL en la PC de los usuarios finales para que funcione en Windows?
oldboy
38

Otro buen paquete de npm es glob .

npm install glob

Es muy poderoso y debe cubrir todas sus necesidades recurrentes.

Editar:

En realidad no estaba perfectamente contento con Glob, así que creé readdirp .

Estoy muy seguro de que su API hace que encontrar archivos y directorios de forma recursiva 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

Thorsten Lorenz
fuente
El mejor módulo en mi opinión. Y es similar a muchos otros proyectos, como Grunt, Mocha, etc. y otros más de 80,000 proyectos. Solo digo.
Yanick Rochon
29

Recomiendo usar node-glob para lograr esa tarea.

var glob = require( 'glob' );  

glob( 'dirname/**/*.js', function( err, files ) {
  console.log( files );
});
Diogo Cardoso
fuente
14

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):
cualquiera que haya leído en los últimos tiempos: el autor dejó de usar este paquete en 2015:

wrench.js está en desuso y no se ha actualizado en bastante tiempo. Recomiendo usar fs-extra para realizar cualquier operación adicional del sistema de archivos.

Domenic
fuente
@Domenic, ¿cómo haces denodifyesto? La devolución de llamada se dispara varias veces (recursivamente). Entonces, usar Q.denodify(wrench.readdirRecursive)solo devuelve el primer resultado.
Onur Yıldırım
1
@ OnurYıldırım sí, esto no es una buena opción para las promesas tal como están. Debería escribir algo que devuelva varias promesas, o algo que espere hasta que se enumeren todas las subdirecciones antes de devolver una promesa. Para lo último, vea github.com/kriskowal/q-io#listdirectorytreepath
Domenic
9

Me encantó la respuesta de chjj anterior y no habría podido crear mi versión del bucle paralelo sin ese comienzo.

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é un Gist . Comentarios bienvenidos Todavía estoy comenzando en el reino NodeJS, así que esa es una forma en que espero aprender más.

kalisjoshua
fuente
9

Con recursividad

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
Loourr
fuente
3
Yo sugeriría que no se incorporan las cadenas de ruta con /pero utilizando el pathmódulo: path.join(searchPath, file). De esa manera, obtendrá las rutas correctas independientemente del sistema operativo.
Moritz Friedrich
8

Use node-dir para producir exactamente la salida que desea

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);
  })
});
Christiaan Westerbeek
fuente
node-dir funcionaba bien, pero cuando lo usé con webpack tengo algunos problemas extraños. Se inserta un  en la función readFiles como en "if (err)  {" causando un error "SyntaxError no detectado: token inesperado {". Estoy desconcertado por este problema y mi reacción inmediata es reemplazar node-dir con algo similar
Parth
1
@Paarth este comentario no te dará respuestas. Escriba una nueva pregunta completa sobre SO o cree un problema en el repositorio de GitHub. Cuando elabore bien su pregunta, incluso podría resolver su problema sin siquiera tener que publicarlo
Christiaan Westerbeek
1
El comentario de @ Parth aún puede ser una advertencia útil para otras personas que están considerando su sugerencia como la solución a su problema. Puede que no hayan estado buscando una respuesta en esta sección de comentarios :)
4

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);
});
recidiva
fuente
2
Esta. Esto es muy ordenado y fácil de usar. Lo bombeé a un módulo, lo requería y funciona como un sándwich de mcdream.
Jay
4

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 (utilizando 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 obtener más casos de uso y ejemplos de uso: https://github.com/nspragg/filehound

Descargo de responsabilidad: soy el autor.

nickool
fuente
4

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

El nodo 8+ tiene Promisify incorporado

Vea mi otra respuesta para un enfoque generador que puede dar resultados aún más rápido.

mpen
fuente
4

Asíncrono

const fs = require('fs')
const path = require('path')

const readdir = (p, done, a = [], i = 0) => fs.readdir(p, (e, d = []) =>
  d.map(f => readdir(a[a.push(path.join(p, f)) - 1], () =>
    ++i == d.length && done(a), a)).length || done(a))

readdir(__dirname, console.log)

Sincronización

const fs = require('fs')
const path = require('path')

const readdirSync = (p, a = []) => {
  if (fs.statSync(p).isDirectory())
    fs.readdirSync(p).map(f => readdirSync(a[a.push(path.join(p, f)) - 1], a))
  return a
}

console.log(readdirSync(__dirname))

Asíncrono legible

function readdir (currentPath, done, allFiles = [], i = 0) {
  fs.readdir(currentPath, function (e, directoryFiles = []) {
    if (!directoryFiles.length)
      return done(allFiles)
    directoryFiles.map(function (file) {
      var joinedPath = path.join(currentPath, file)
      allFiles.push(joinedPath)
      readdir(joinedPath, function () {
        i = i + 1
        if (i == directoryFiles.length)
          done(allFiles)}
      , allFiles)
    })
  })
}

readdir(__dirname, console.log)

Nota: ambas versiones seguirán enlaces simbólicos (igual que el original fs.readdir)

Afanasii Kurakin
fuente
3

Echa un vistazo a la biblioteca final-fs . Proporciona una readdirRecursivefunción:

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
    });
Szymon Wygnański
fuente
2

Implementación de promesa independiente

Estoy usando la biblioteca de promesas 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 includeDirque incluirá directorios en la lista de archivos si está configurado en true.

JayQuerie.com
fuente
1

Aquí hay otra implementación más. Ninguna de las soluciones anteriores tiene limitadores, por lo que si la estructura de su directorio es grande, todos 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 más simples para estructuras de directorios pequeñas.

Boson Mono
fuente
1

Modifiqué la respuesta basada en 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); {
});
Phil Mander
fuente
1

Por diversión, aquí hay una versión basada en flujo que funciona con la biblioteca de secuencias highland.js. Fue 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))
Michael Connor
fuente
1

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 la promesa de una matriz, por lo que puede usarla como:

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

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

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

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));
alexcres
fuente
0

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

  });
}
vvo
fuente
0

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

Una ventaja adicional es que también mirará los archivos y volverá a llamar a la devolución de llamada. Hay toneladas de opciones de configuración para hacerlo extremadamente flexible.

Acabo de rehacer la guardgema de ruby ​​usando loaddir en poco tiempo

Funkodebat
fuente
0

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

Mi objetivo es hacer que la rutina de búsqueda pueda detenerse en cualquier lugar, y para un archivo encontrado, indica 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. 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);
manbaum
fuente
0

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;
}
Daniel
fuente
0

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;
    }
}
clinyong
fuente
Está asumiendo que cada archivo en el directorio raíz es una carpeta aquí.
xechelonx
0

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 tiene una ruta llamada '/ base de datos' en la raíz de proyectos de su nodo. Una vez que se resuelva esta promesa, debería escupir una matriz de cada archivo en '/ database'.

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