node.js fs.readdir rekursive Verzeichnissuche

266

Irgendwelche Ideen zu einer asynchronen Verzeichnissuche mit fs.readdir? Mir ist klar, dass wir eine Rekursion einführen und die Leseverzeichnisfunktion mit dem nächsten zu lesenden Verzeichnis aufrufen könnten, aber ich bin ein wenig besorgt darüber, dass es nicht asynchron ist ...

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

Auf der Suche nach Ausgabe wie ...

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

Antworten:

377

Grundsätzlich gibt es 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, bis eine Iteration abgeschlossen ist, bevor sie zur nächsten Iteration übergeht. Dies garantiert, dass jede Iteration der Schleife der Reihe nach abgeschlossen wird. In einer parallelen Schleife werden alle Iterationen gleichzeitig gestartet, und eine kann vor der anderen abgeschlossen werden. Sie ist jedoch viel schneller als eine serielle Schleife. In diesem Fall ist es wahrscheinlich besser, eine parallele Schleife zu verwenden, da es keine Rolle spielt, in welcher Reihenfolge der Spaziergang abgeschlossen wird, solange die Ergebnisse abgeschlossen sind und zurückgegeben werden (es sei denn, Sie möchten, dass sie in der richtigen Reihenfolge vorliegen).

Eine parallele Schleife würde folgendermaßen 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 folgendermaßen 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 (WARNUNG: Die Ergebnisliste ist riesig, wenn Sie viele Dinge in Ihrem Home-Verzeichnis haben):

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

EDIT: Verbesserte Beispiele.

chjj
quelle
10
Beachten Sie, dass die Antwort "Parallelschleife" von chjj oben einen Fehler aufweist, wenn ein leerer Ordner durchlaufen wird. Das Update lautet: var pending = list.length; if (! ausstehend) erledigt (null, Ergebnisse); // füge diese Zeile hinzu! list.forEach (Funktion (Datei) {...
Vasil Daskalopoulos
27
file = dir + '/' + file;Dies wird nicht empfohlen. Sie sollten verwenden: var path = require('path'); file = path.resolve(dir, file);
Leiko
7
@onetrickpony, denn wenn Sie verwenden, erhalten path.resolve(...)Sie einen richtigen Pfad, egal ob Sie unter Windows oder Unix arbeiten :) Das bedeutet, dass Sie so etwas wie C:\\some\\foo\\pathunter Windows und /some/foo/pathauf Unix-Systemen erhalten
Leiko
19
Ich habe abgelehnt, weil Ihre Antwort großartig war, als Sie sie 2011 zum ersten Mal geschrieben haben, aber 2014 verwenden die Leute Open-Source-Module und schreiben selbst weniger Code und tragen zu den Modulen bei, von denen sie und so viele andere abhängig sind. Versuchen Sie beispielsweise Node-Dir , um genau die Ausgabe zu erhalten, die von @crawf mit dieser Codezeile benötigt wird:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek
5
Für alle, die über die !--Syntax verwirrt sind , wurde eine Frage gestellt
Tas
145

In diesem Fall wird die maximale Anzahl neuer, in Knoten 8 verfügbarer, witziger Funktionen verwendet, einschließlich Versprechen, Nutzen / Versprechen, Destrukturieren, asynchrones Warten, Zuordnen + Reduzieren und mehr, sodass Ihre Mitarbeiter sich am Kopf kratzen, wenn sie herausfinden möchten, was passiert es geht voran.

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

Knoten 10.10+

Aktualisiert für Knoten 10+ mit noch mehr 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);
}

Beachten Sie, dass Sie ab Knoten 11.15.0 files.flat()anstelle von verwenden könnenArray.prototype.concat(...files) zu .

Knoten 11+

Wenn Sie alle völlig in die Luft jagen möchten, können Sie die folgende Version mit asynchronen Iteratoren verwenden . 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 jemand interessiert ist, habe ich hier mehr über asynchrone Iteratoren geschrieben: https://qwtel.com/posts/software/async-generators-in-the-wild/

qwtel
quelle
5
Die Benennung von subdirund subdirsist irreführend, da es sich tatsächlich um Dateien handelt (ich schlage etwas wie itemInDiroder item_in_diroder sogar einfach itemstattdessen vor), aber diese Lösung fühlt sich sauberer an als die akzeptierte und ist viel weniger Code. Ich finde es auch nicht viel komplizierter als den Code in der akzeptierten Antwort. +1
Zelphir Kaltstahl
1
Sie könnten dies noch mehr Whizbang machen, indem Sie es verwenden require(fs).promisesund einfach ganz fallen lassen util.promisify. Persönlich alias ich fs zu fs.promises.
MushinNoShin
2
Wir können dies mit einer kleinen Änderung beschleunigen: Übergeben Sie das zweite Argument an readdirAKA, das Optionsobjekt, so readdir(dir, {withFileTypes: true})dass alle Elemente mit ihren Typinformationen zurückgegeben werden. Wir müssen also überhaupt nicht aufrufen stat, um die Informationen zu erhalten, readdirdie uns jetzt zur Verfügung stehen zurück. Dies erspart uns zusätzliche Systemanrufe. Details hier
Cacoder
1
@cacoder Aktualisiert um einzuschließen withFileTypes. Danke für den Tipp.
Qwtel
in Knoten 10.10+, wenn Sie ersetzen return Array.prototype.concat(...files);mit let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));Ihnen sicher , dass die Verz machen zurückkehren ein „/“ und kein „\“. Wenn Ihnen Regex nichts ausmacht, können Sie dies auch tunreturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro
106

Für den Fall, dass es jemand nützlich findet, habe ich auch eine synchrone Version zusammengestellt.

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. ZB results.push(file);durch folgenden Code ersetzen . Nach Bedarf anpassen:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);
Victor Powell
quelle
60
Ich mag diese Lösung, bis auf Ihren Mangel an Semikolons!
Mpen
Das ist einfach. Aber auch ein bisschen naiv. Kann einen Stapelüberlauf verursachen, wenn ein Verzeichnis einen Link zu einem übergeordneten Verzeichnis enthält. Vielleicht lstatstattdessen verwenden? Oder fügen Sie eine Rekursivitätsprüfung hinzu, um die Rekursivitätsstufe zu begrenzen.
Conradkleinespel
14
Erwägen
16
@mpen Semikolons sind überflüssig
Ally
Das funktioniert auch am besten für mich. Ich habe jedoch auch einen Filter hinzugefügt, um nach einer bestimmten Dateierweiterung zu filtern.
Brian
87

A. Sehen Sie sich das Dateimodul an . Es hat eine Funktion namens walk:

file.walk (Start, Rückruf)

Navigiert durch einen Dateibaum, ruft für jedes Verzeichnis einen Rückruf auf und übergibt (null, dirPath, dirs, files).

Das kann für Sie sein! Und ja, es ist asynchron. Ich denke jedoch, dass Sie die vollständigen Pfade selbst aggregieren müssten, wenn Sie sie benötigen würden.

B. Eine Alternative und sogar einer meiner Favoriten: Verwenden Sie dazu das Unix find. Warum nochmal etwas machen, das schon programmiert wurde? Vielleicht nicht genau das, was Sie brauchen, aber dennoch einen Besuch 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 verfügt über einen netten integrierten Caching-Mechanismus, der nachfolgende Suchvorgänge sehr schnell macht, solange sich nur wenige Ordner geändert haben.

Johann Philipp Strathausen
quelle
9
Ist das nur UNIX?
Mohsen
Hatte eine Frage zu Beispiel B: Für execFile () (und exec ()) sind stderr und stdout Puffer. Müssen Sie also nicht stdout.toString.split ("\ n") ausführen, da Puffer keine Zeichenfolgen sind?
Cheruvim
8
schön, aber nicht plattformübergreifend.
f0ster
Übrigens: Nein, A ist nicht nur Unix! Nur B ist nur Unix. Windows 10 verfügt jetzt jedoch über ein Linux-Subsystem. Sogar B würde heutzutage nur unter Windows funktionieren.
Johann Philipp Strathausen
Müsste die WSL nicht auf dem Endbenutzer-PC aktiviert sein, damit sie unter Windows funktioniert?
Oldboy
38

Ein weiteres schönes npm-Paket ist glob .

npm install glob

Es ist sehr leistungsfähig und sollte alle Ihre wiederkehrenden Anforderungen abdecken.

Bearbeiten:

Ich war eigentlich nicht ganz zufrieden mit glob, also habe ich readdirp erstellt .

Ich bin sehr zuversichtlich, dass die API das rekursive Auffinden von Dateien und Verzeichnissen und das Anwenden bestimmter Filter sehr einfach macht.

Lesen Sie die Dokumentation durch , um eine bessere Vorstellung davon zu erhalten, was es tut, und installieren Sie es über:

npm install readdirp

Thorsten Lorenz
quelle
Bestes Modul meiner Meinung nach. Und viele andere Projekte wie Grunt, Mocha usw. und andere über 80'000 Projekte. Nur sagen.
Yanick Rochon
29

Ich empfehle die Verwendung von Node-Glob , um diese Aufgabe zu erfüllen.

var glob = require( 'glob' );  

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

Wenn Sie ein npm-Paket verwenden möchten, ist der Schraubenschlüssel ziemlich gut.

var wrench = require("wrench");

var files = wrench.readdirSyncRecursive("directory");

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

EDIT (2018):
Jeder, der in letzter Zeit durchgelesen hat: Der Autor hat dieses Paket im Jahr 2015 abgelehnt:

wrench.js ist veraltet und wurde seit einiger Zeit nicht mehr aktualisiert. Ich empfehle dringend, fs-extra zu verwenden, um zusätzliche Dateisystemoperationen durchzuführen.

Domenic
quelle
@Domenic, wie machst du denodifydas? Der Rückruf wird mehrmals (rekursiv) ausgelöst. Wenn Sie also verwenden, wird Q.denodify(wrench.readdirRecursive)nur das erste Ergebnis zurückgegeben.
Onur Yıldırım
1
@ OnurYıldırım Ja, das passt nicht zu Versprechungen wie sie sind. Sie müssten etwas schreiben, das mehrere Versprechen zurückgibt, oder etwas, das wartet, bis alle Unterverzeichnisse aufgelistet sind, bevor Sie ein Versprechen zurückgeben. Für letzteres siehe github.com/kriskowal/q-io#listdirectorytreepath
Domenic
9

Ich mochte die Antwort von chjj oben und hätte ohne diesen Start meine Version der Parallelschleife nicht erstellen können.

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;

Ich habe auch einen Kern erstellt . Kommentare willkommen. Ich fange immer noch im NodeJS-Bereich an, also hoffe ich, dass ich auf diese Weise mehr erfahren kann.

Kalisjoshua
quelle
8

Verwenden Sie Node-Dir , um genau die Ausgabe zu erzeugen, die Sie mögen

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
quelle
Node-Dir hat gut funktioniert, aber als ich es mit Webpack verwendet habe, habe ich einige seltsame Probleme. In die Funktion readFiles wird ein  eingefügt, wie in "if (err) {", was einen "nicht erfassten SyntaxError: Unexpected token {" -Fehler verursacht. Ich bin verblüfft über dieses Problem und meine unmittelbare Reaktion besteht darin, das Knoten-Verzeichnis durch etwas Ähnliches zu ersetzen
Parth
1
@Parth dieser Kommentar wird Ihnen keine Antworten geben. Schreiben Sie eine neue vollständige Frage zu SO oder erstellen Sie ein Problem im GitHub-Repository. Wenn Sie Ihre Frage gut ausarbeiten, können Sie Ihr Problem möglicherweise sogar lösen, ohne es veröffentlichen zu müssen
Christiaan Westerbeek,
1
Der Kommentar von @ Parth kann immer noch eine nützliche Warnung für andere sein, die Ihren Vorschlag als Lösung für ihr Problem betrachten. Sie haben möglicherweise nicht nach einer Antwort in diesem Kommentarbereich
8

Mit Rekursion

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

Berufung

getFiles(path, files)
console.log(files) // will log all files in directory
Loourr
quelle
3
Ich würde vorschlagen, die Pfadzeichenfolgen nicht /mit dem pathModul zu verbinden, sondern es zu verwenden : path.join(searchPath, file). Auf diese Weise erhalten Sie unabhängig vom Betriebssystem die richtigen Pfade.
Moritz Friedrich
4

Ich habe dies kürzlich codiert und dachte, es wäre sinnvoll, dies hier zu teilen. Der Code verwendet die asynchrone Bibliothek .

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

Sie können es so verwenden:

scan('/some/dir', '.ext', function(err, files) {
  // Do something with files that ends in '.ext'.
  console.log(files);
});
rückfällig
quelle
2
Dies. Dies ist so ordentlich und einfach zu bedienen. Ich habe es in ein Modul gepumpt, es benötigt und es funktioniert wie ein Mcdream-Sandwich.
Jay
4

Eine Bibliothek namens Filehound ist eine weitere Option. Es wird rekursiv ein bestimmtes Verzeichnis durchsucht (standardmäßig Arbeitsverzeichnis). Es unterstützt verschiedene Filter, Rückrufe, Versprechen und Synchronisierungssuchen.

Durchsuchen Sie beispielsweise das aktuelle Arbeitsverzeichnis nach allen Dateien (mithilfe von Rückrufen):

const Filehound = require('filehound');

Filehound.create()
.find((err, files) => {
    if (err) {
        return console.error(`error: ${err}`);
    }
    console.log(files); // array of files
});

Oder verspricht und spezifiziert ein bestimmtes Verzeichnis:

const Filehound = require('filehound');

Filehound.create()
.paths("/tmp")
.find()
.each(console.log);

Weitere Anwendungsfälle und Anwendungsbeispiele finden Sie in den Dokumenten: https://github.com/nspragg/filehound

Haftungsausschluss: Ich bin der Autor.

Nickool
quelle
4

Mit async / await sollte dies funktionieren:

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

Sie können bluebird.Promisify oder dies verwenden:

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

In Knoten 8+ ist Promisify integriert

In meiner anderen Antwort finden Sie einen Generatoransatz, mit dem Sie noch schneller Ergebnisse erzielen können.

mpen
quelle
4

Async

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)

Synchronisieren

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

Async lesbar

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)

Hinweis: Beide Versionen folgen Symlinks (wie das Original fs.readdir).

Afanasii Kurakin
quelle
3

Schauen Sie sich die final-fs- Bibliothek an. Es bietet eine readdirRecursiveFunktion:

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
quelle
2

Implementierung eines eigenständigen Versprechens

In diesem Beispiel verwende ich die Versprechungsbibliothek when.js.

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

Ich habe einen optionalen Parameter eingefügt, includeDirder Verzeichnisse in die Dateiliste aufnimmt, wenn er auf gesetzt ist true.

JayQuerie.com
quelle
1

Hier ist noch eine weitere Implementierung. Keine der oben genannten Lösungen hat irgendwelche Begrenzer. Wenn Ihre Verzeichnisstruktur also groß ist, werden sie alle kaputt gehen und schließlich keine Ressourcen mehr haben.

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

Die Verwendung einer Parallelität von 50 funktioniert ziemlich gut und ist fast so schnell wie einfachere Implementierungen für kleine Verzeichnisstrukturen.

Affe Boson
quelle
1

Ich habe die auf Promise basierende Antwort von Trevor Senior geändert , um mit Bluebird zu arbeiten

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
quelle
1

Zum Spaß gibt es hier eine Flow-basierte Version, die mit der Stream-Bibliothek von highland.js funktioniert. Es wurde von Victor Vu mitverfasst.

###
  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
quelle
1

Verwenden Sie Versprechen ( Q ), um dies in einem funktionalen Stil zu lösen:

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

Es gibt ein Versprechen eines Arrays zurück, sodass Sie es wie folgt verwenden können:

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

Ich muss die Promise-basierte Schleiferbibliothek zur Liste hinzufügen .

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

Verwenden von Bluebird Versprechen.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
quelle
0

Weil jeder sein eigenes schreiben sollte, habe ich eins gemacht.

walk (dir, cb, endCb) cb (Datei) endCb (err | null)

DRECKIG

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
quelle
0

Schauen Sie sich loaddir https://npmjs.org/package/loaddir an

npm install loaddir

  loaddir = require('loaddir')

  allJavascripts = []
  loaddir({
    path: __dirname + '/public/javascripts',
    callback: function(){  allJavascripts.push(this.relativePath + this.baseName); }
  })

Sie können fileNameanstelle von verwenden, baseNamewenn Sie die Erweiterung auch benötigen.

Ein zusätzlicher Bonus ist, dass auch die Dateien angezeigt und der Rückruf erneut aufgerufen wird. Es gibt unzählige Konfigurationsoptionen, um es extrem flexibel zu machen.

Ich habe den guardEdelstein in kurzer Zeit mit loaddir aus Rubin gemacht

Funkodebat
quelle
0

Das ist meine Antwort. Hoffe es kann jemandem helfen.

Mein Fokus liegt darauf, dass die Suchroutine an einer beliebigen Stelle anhalten kann und für eine gefundene Datei die relative Tiefe zum ursprünglichen Pfad angibt.

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
quelle
0

Hier ist eine rekursive Methode zum Abrufen aller Dateien einschließlich der Unterverzeichnisse.

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
quelle
0

Ein weiterer einfacher und hilfreicher

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
quelle
Sie gehen davon aus, dass jede Datei im Stammverzeichnis hier ein Ordner ist.
Xechelonx
0

So verwende ich die Funktion nodejs fs.readdir, um ein Verzeichnis rekursiv zu durchsuchen.

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

Angenommen, Sie haben einen Pfad mit dem Namen '/ database' im Stammverzeichnis Ihres Knotenprojekts. Sobald dieses Versprechen gelöst ist, sollte es ein Array jeder Datei unter '/ database' ausspucken.

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