Búsqueda recursiva en el directorio de Node.js fs.readdir

Resuelto crawf asked hace 13 años • 49 respuestas

¿Alguna idea sobre una búsqueda de directorio asíncrona usando fs.readdir? Me doy cuenta de que podríamos introducir la recursividad y llamar a la función de lectura de directorio con el siguiente directorio a leer, pero me preocupa un poco que no sea asíncrono...

¿Algunas ideas? Miré node-walk , que es excelente, pero no me proporciona solo los archivos en una matriz, como lo hace readdir. A pesar de

Buscando resultados como...

['file1.txt', 'file2.txt', 'dir/file3.txt']
crawf avatar Apr 29 '11 10:04 crawf
Aceptado

Hay básicamente dos maneras de lograr esto. En un entorno asíncrono notarás 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; 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 la 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 y cuando 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 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 personal (ADVERTENCIA: la lista de resultados será enorme si tiene muchas cosas en su directorio personal):

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

EDITAR: ejemplos mejorados.

chjj avatar Apr 29 '2011 04:04 chjj

Este utiliza la cantidad máxima de funciones nuevas y de moda disponibles en el nodo 8, incluidas Promises, util/promisify, destructuring, async-await, map+reduce y más, lo que hace que sus compañeros de trabajo se rasquen la cabeza mientras intentan descubrir 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 maravillas:

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 utilizar files.flat()en lugar de Array.prototype.concat(...files)para aplanar la matriz de archivos.

Nodo 11+

Si quieres hacer volar la cabeza de todos por completo, puedes usar la siguiente versión usando iteradores asíncronos . Además de ser realmente interesante, 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 devolución 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/

Nodo 20+

A partir del Nodo 20, fs.readdirtiene una { recursive: true }opción

const files = await fs.readdir(dir, { recursive: true });
qwtel avatar Jul 16 '2017 16:07 qwtel