391 Stimmen

Node.js fs.readdir rekursive Verzeichnissuche

Irgendwelche Ideen für eine asynchrone Verzeichnissuche mit fs.readdir? Mir ist klar, dass wir eine Rekursion einführen und die Funktion read directory mit dem nächsten zu lesenden Verzeichnis aufrufen könnten, aber ich bin ein wenig besorgt, dass es nicht asynchron ist...

Irgendwelche Ideen? Ich habe mir angesehen node-walk was großartig ist, aber mir nicht nur die Dateien in einem Array liefert, wie es readdir tut. Obwohl

Ich suche eine Ausgabe wie...

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

444voto

chjj Punkte 13926

Es gibt grundsätzlich zwei Möglichkeiten, dies zu erreichen. In einer asynchronen Umgebung werden Sie feststellen, dass es zwei Arten von Schleifen gibt: serielle und parallele. Eine serielle Schleife wartet auf den Abschluss einer Iteration, bevor sie zur nächsten Iteration übergeht. In einer parallelen Schleife werden alle Iterationen gleichzeitig gestartet, und es kann sein, dass eine Iteration vor einer anderen abgeschlossen wird, aber sie ist viel schneller als eine serielle Schleife. In diesem Fall ist es also wahrscheinlich besser, eine parallele Schleife zu verwenden, da es keine Rolle spielt, in welcher Reihenfolge der Durchlauf erfolgt, solange er abgeschlossen ist und die Ergebnisse zurückgegeben werden (es sei denn, Sie möchten sie in der richtigen Reihenfolge haben).

Eine parallele Schleife würde wie folgt aussehen:

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

Eine serielle Schleife würde wie folgt aussehen:

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

Und um es in Ihrem Home-Verzeichnis zu testen (ACHTUNG: die Ergebnisliste wird riesig, wenn Sie viele Dateien in Ihrem Home-Verzeichnis haben):

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

EDIT: Verbesserte Beispiele.

379voto

qwtel Punkte 3797

In diesem Projekt wird ein Maximum an neuen Funktionen verwendet, die in Node 8 verfügbar sind, darunter Promises, util/promisify, Destrukturierung, async-await, map+reduce und vieles mehr, so dass sich Ihre Kollegen am Kopf kratzen, wenn sie versuchen herauszufinden, was hier vor sich geht.

Knoten 8+

Keine externen Abhängigkeiten.

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), []);
}

Verwendung

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

Node 10.10+

Aktualisiert für Node 10+ mit noch mehr Funktionen:

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

Beachten Sie, dass Sie ab Node 11.15.0 Folgendes verwenden können files.flat() anstelle von Array.prototype.concat(...files) um das Dateiarray zu reduzieren.

Knoten 11+

Wenn Sie alle in die Luft jagen wollen, können Sie die folgende Version verwenden asynchrone Iteratoren . Es ist nicht nur wirklich cool, sondern ermöglicht es den Verbrauchern auch, die Ergebnisse einzeln abzurufen, wodurch es sich besser für wirklich große Verzeichnisse eignet.

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

Die Verwendung hat sich geändert, da der Rückgabetyp jetzt ein asynchroner Iterator anstelle eines Versprechens ist

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

Falls es jemanden interessiert, habe ich hier mehr über asynchrone Iteratoren geschrieben: https://qwtel.com/posts/software/async-generators-in-the-wild/

150voto

Victor Powell Punkte 1648

Nur für den Fall, dass jemand sie nützlich findet, habe ich auch eine synchron Version.

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

Tipp: Um beim Filtern weniger Ressourcen zu verbrauchen. Filtern Sie innerhalb dieser Funktion selbst. Z.B. Ersetzen Sie results.push(file); mit dem folgenden Code. Passen Sie ihn nach Bedarf an:

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

92voto

A. Werfen Sie einen Blick auf die Dateimodul . Es hat eine Funktion namens walk:

file.walk(start, callback)

Navigiert durch einen Dateibaum und ruft Callback für jede (null, dirPfad, Verzeichnisse, Dateien).

Das könnte etwas für Sie sein! Und ja, es ist asynchron. Allerdings denke ich, Sie müssten die vollständigen Pfade selbst aggregieren, wenn Sie sie benötigen.

B. Eine Alternative, und sogar eine meiner Favoriten, ist die Verwendung des Unix find dafür. Warum etwas noch einmal machen, was bereits programmiert wurde? Vielleicht nicht genau das, was Sie brauchen, aber trotzdem einen Blick wert:

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 hat einen netten eingebauten Caching-Mechanismus, der nachfolgende Suchen sehr schnell macht, solange sich nur wenige Ordner geändert haben.

55voto

Diogo Cardoso Punkte 20469

Ich empfehle die Verwendung von Knoten-Globus um diese Aufgabe zu erfüllen.

var glob = require( 'glob' );  

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X