node.js fs.readdir recherche récursive dans le répertoire

268

Des idées sur une recherche de répertoire asynchrone en utilisant fs.readdir? Je me rends compte que nous pourrions introduire la récursivité et appeler la fonction de lecture de répertoire avec le prochain répertoire à lire, mais je suis un peu inquiet à l'idée qu'il ne soit pas asynchrone ...

Des idées? J'ai regardé node-walk, ce qui est génial, mais ne me donne pas uniquement les fichiers d'un tableau, comme readdir. Bien que

Vous recherchez une sortie comme ...

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

Réponses:

379

Il y a essentiellement deux façons d'y parvenir. Dans un environnement asynchrone, vous remarquerez qu'il existe deux types de boucles: série et parallèle. Une boucle série attend la fin d'une itération avant de passer à l'itération suivante - cela garantit que chaque itération de la boucle se termine dans l'ordre. Dans une boucle parallèle, toutes les itérations sont démarrées en même temps, et l'une peut se terminer avant une autre, cependant, c'est beaucoup plus rapide qu'une boucle série. Donc, dans ce cas, il est probablement préférable d'utiliser une boucle parallèle car peu importe l'ordre dans lequel la marche se termine, tant qu'elle se termine et renvoie les résultats (sauf si vous les souhaitez dans l'ordre).

Une boucle parallèle ressemblerait à ceci:

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

Une boucle série ressemblerait à ceci:

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

Et pour le tester sur votre répertoire personnel (ATTENTION: la liste des résultats sera énorme si vous avez beaucoup de choses dans votre répertoire personnel):

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

EDIT: exemples améliorés.

chjj
la source
10
Attention, la réponse "boucle parallèle" de chjj ci-dessus a un bug dans les cas où un dossier vide est parcouru. Le correctif est: var en attente = list.length; si (! en attente) fait (null, résultats); // ajoutez cette ligne! list.forEach (fonction (fichier) {...
Vasil Daskalopoulos
27
file = dir + '/' + file;Ce n'est pas recommandé. Vous devez utiliser: var path = require('path'); file = path.resolve(dir, file);
Leiko
7
@onetrickpony parce que si vous utilisez, path.resolve(...)vous obtiendrez un chemin d'accès approprié que vous soyez sur Windows ou Unix :) Cela signifie que vous obtiendrez quelque chose comme C:\\some\\foo\\pathsur Windows et /some/foo/pathsur les systèmes Unix
Leiko
19
J'ai voté contre parce que votre réponse était excellente lorsque vous l'avez écrite pour la première fois en 2011, mais en 2014, les gens utilisent des modules open source et écrivent moins de code eux-mêmes et contribuent aux modules dont eux et tant d'autres dépendent. Par exemple, essayez node-dir pour obtenir exactement la sortie requise par @crawf en utilisant cette ligne de code:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek
5
Pour toute personne confuse au sujet de la !--syntaxe, une question a été posée à ce sujet
Tas
147

Celui-ci utilise le maximum de nouvelles fonctionnalités buzzwordy disponibles dans le nœud 8, y compris Promises, util / promisify, destructuring, async-wait, map + réduire et plus, ce qui fait que vos collègues se grattent la tête en essayant de comprendre ce qui se passe.

Noeud 8+

Pas de dépendances externes.

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

Usage

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

Noeud 10.10+

Mis à jour pour le noeud 10+ avec encore plus de 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);
}

Notez qu'à partir du noeud 11.15.0, vous pouvez utiliser files.flat()au lieu de Array.prototype.concat(...files)pour aplatir le tableau de fichiers.

Noeud 11+

Si vous voulez faire exploser complètement la tête de tout le monde, vous pouvez utiliser la version suivante à l'aide d' itérateurs asynchrones . En plus d'être vraiment cool, il permet également aux consommateurs de retirer les résultats un par un, ce qui le rend mieux adapté aux très gros répertoires.

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

L'utilisation a changé car le type de retour est désormais un itérateur asynchrone au lieu d'une promesse

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

Si quelqu'un est intéressé, j'ai écrit plus sur les itérateurs asynchrones ici: https://qwtel.com/posts/software/async-generators-in-the-wild/

qwtel
la source
5
La dénomination de subdiret subdirsest trompeuse, car il peut s'agir en fait de fichiers (je suggère quelque chose comme itemInDirou item_in_dirou simplement à la itemplace.), Mais cette solution semble plus propre que celle acceptée et est beaucoup moins de code. Je ne trouve pas non plus cela beaucoup plus compliqué que le code dans la réponse acceptée. +1
Zelphir Kaltstahl
1
Vous pouvez faire encore plus de bruit en utilisant require(fs).promiseset simplement laisser tomber util.promisifycomplètement. Personnellement, je alias fs à fs.promises.
MushinNoShin
2
Nous pouvons rendre cela plus rapide avec un petit changement: en passant le 2ème argument à readdirAKA l'objet options comme readdir(dir, {withFileTypes: true})ceci retournera tous les éléments avec leurs informations de type, donc nous n'aurons pas besoin d'appeler statdu tout pour obtenir les informations qui readdirnous donnent maintenant arrière. Cela nous évite d'avoir à effectuer des appels sys supplémentaires. Détails ici
cacoder
1
@cacoder Mis à jour pour inclure withFileTypes. Merci pour le conseil.
qwtel
dans le noeud 10.10+, si vous remplacez return Array.prototype.concat(...files);par, let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));vous pouvez vous assurer que les répertoires renvoient un "/" et non un "\". Si cela ne vous dérange pas, vous pouvez également le fairereturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro
106

Au cas où quelqu'un le trouverait utile, j'ai également mis au point une version synchrone .

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

Conseil: pour utiliser moins de ressources lors du filtrage. Filtrer dans cette fonction elle-même. Par exemple, remplacez results.push(file);par le code ci-dessous. Ajustez au besoin:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);
Victor Powell
la source
60
J'aime cette solution, sauf pour votre manque de points-virgules!
mpen
C’est simple. Mais aussi un peu naïf. Peut provoquer un débordement de pile si un répertoire contient un lien vers un répertoire parent. Peut-être utiliser à la lstatplace? Ou bien ajoutez une vérification de récursivité pour limiter le niveau de récursivité.
conradkleinespel
14
Pensez à utiliser file = require ("path").
Join
16
@mpen Les points-virgules sont redondants
Ally
Cela fonctionne aussi mieux pour moi. Bien que j'aie également ajouté un filtre pour filtrer une extension de fichier spécifique.
Brian
87

A. Jetez un œil au module de fichiers . Il a une fonction appelée marche:

file.walk (démarrage, rappel)

Navigue dans une arborescence de fichiers, appelle un rappel pour chaque répertoire, transmet (null, dirPath, dirs, files).

C'est peut-être pour vous! Et oui, c'est asynchrone. Cependant, je pense que vous devrez agréger le chemin complet vous-même, si vous en avez besoin.

B. Une alternative, et même une de mes préférées: utilisez l'Unix findpour ça. Pourquoi refaire quelque chose qui a déjà été programmé? Peut-être pas exactement ce dont vous avez besoin, mais ça vaut quand même le coup d'œil:

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 a un joli mécanisme de mise en cache intégré qui rend les recherches suivantes très rapides, tant que seuls quelques dossiers ont changé.

Johann Philipp Strathausen
la source
9
Est-ce UNIX uniquement?
Mohsen
Vous avez une question à propos de l'exemple B: Pour execFile () (et exec ()), stderr et stdout sont des tampons.
Cheruvim
8
agréable, mais pas multiplateforme.
f0ster
Au fait: non, A n'est pas seulement Unix! Seul B est Unix uniquement. Cependant, Windows 10 est désormais livré avec un sous-système Linux. Ainsi, même B fonctionnerait simplement sur Windows de nos jours.
Johann Philipp Strathausen
WSL ne devrait-il pas être activé sur le PC de l'utilisateur final pour qu'il fonctionne sous Windows?
oldboy
38

Un autre joli paquet npm est glob .

npm install glob

Il est très puissant et devrait couvrir tous vos besoins récurrents.

Éditer:

En fait, je n'étais pas parfaitement satisfait de glob, alors j'ai créé readdirp .

Je suis très convaincu que son API facilite la recherche récursive de fichiers et de répertoires et l'application de filtres spécifiques très facilement.

Lisez sa documentation pour avoir une meilleure idée de ce qu'il fait et installez via:

npm install readdirp

Thorsten Lorenz
la source
Le meilleur module à mon avis. Et est semblable à de nombreux autres projets, comme Grunt, Mocha, etc. et à d'autres 80'000 + autres projets. Je dis juste.
Yanick Rochon
29

Je recommande d'utiliser node-glob pour accomplir cette tâche.

var glob = require( 'glob' );  

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

Si vous souhaitez utiliser un package npm, la clé est plutôt bonne.

var wrench = require("wrench");

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

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

EDIT (2018):
Toute personne lisant ces derniers temps: L'auteur a déprécié ce package en 2015:

wrench.js est obsolète et n'a pas été mis à jour depuis un certain temps. Je recommande fortement d'utiliser fs-extra pour effectuer des opérations supplémentaires sur le système de fichiers.

Domenic
la source
@Domenic, comment faites-vous denodifycela? Le rappel est déclenché plusieurs fois (récursivement). Ainsi, l'utilisation Q.denodify(wrench.readdirRecursive)ne renvoie que le premier résultat.
Onur Yıldırım
1
@ OnurYıldırım ouais, ce n'est pas un bon ajustement pour les promesses en l'état. Vous devrez écrire quelque chose qui renvoie plusieurs promesses, ou quelque chose qui attend que tous les sous-répertoires soient énumérés avant de renvoyer une promesse. Pour ce dernier, voir github.com/kriskowal/q-io#listdirectorytreepath
Domenic
9

J'ai adoré la réponse de chjj ci-dessus et je n'aurais pas pu créer ma version de la boucle parallèle sans ce début.

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;

J'ai également créé un Gist . Commentaires bienvenus. Je commence toujours dans le domaine NodeJS, c'est donc une façon dont j'espère en savoir plus.

kalisjoshua
la source
9

Avec récursivité

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

Appel

getFiles(path, files)
console.log(files) // will log all files in directory
Loourr
la source
3
Je vous suggère de ne pas adhérer à des chaînes de chemin avec /mais en utilisant le pathmodule de : path.join(searchPath, file). De cette façon, vous obtiendrez des chemins corrects indépendamment du système d'exploitation.
Moritz Friedrich
8

Utilisez node-dir pour produire exactement la sortie que vous aimez

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
la source
node-dir fonctionnait bien, mais quand je l'ai utilisé avec webpack, j'ai des problèmes étranges. Un  est inséré dans la fonction readFiles comme dans "if (err)  {", ce qui provoque une erreur "SyntaxError non détectée: jeton inattendu {". Je suis déconcerté par ce problème et ma réaction immédiate est de remplacer node-dir par quelque chose de similaire
Parth
1
@Parth ce commentaire ne va pas vous donner de réponses. Écrivez une nouvelle question complète sur SO ou créez un problème dans le référentiel GitHub. Lorsque vous développez bien votre question, vous pourriez même être en mesure de résoudre votre problème sans même avoir à le poster
Christiaan Westerbeek
1
Le commentaire de @ Parth peut encore être un avertissement utile pour ceux qui considèrent votre suggestion comme la solution à leur problème. Ils n'ont peut-être pas cherché de réponse dans cette section de commentaires :)
4

J'ai codé cela récemment et j'ai pensé qu'il serait logique de partager cela ici. Le code utilise la bibliothèque async .

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

Vous pouvez l'utiliser comme ceci:

scan('/some/dir', '.ext', function(err, files) {
  // Do something with files that ends in '.ext'.
  console.log(files);
});
récidive
la source
2
Ce. C'est tellement bien rangé et simple à utiliser. Je l'ai pompé dans un module, je l'ai requis et cela fonctionne comme un sandwich mcdream.
Jay
4

Une bibliothèque appelée Filehound est une autre option. Il recherchera récursivement un répertoire donné (répertoire de travail par défaut). Il prend en charge divers filtres, rappels, promesses et recherches de synchronisation.

Par exemple, recherchez tous les fichiers dans le répertoire de travail actuel (à l'aide de rappels):

const Filehound = require('filehound');

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

Ou promet et spécifie un répertoire spécifique:

const Filehound = require('filehound');

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

Consultez la documentation pour d'autres cas d'utilisation et des exemples d'utilisation: https://github.com/nspragg/filehound

Avertissement: je suis l'auteur.

nickool
la source
4

En utilisant async / wait, cela devrait fonctionner:

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

Vous pouvez utiliser bluebird.Promisify ou ceci:

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

Le nœud 8+ intègre Promisify

Voir mon autre réponse pour une approche de générateur qui peut donner des résultats encore plus rapidement.

mpen
la source
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)

Sync

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

Lecture asynchrone

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)

Remarque: les deux versions suivront des liens symboliques (identiques à l'original fs.readdir)

Afanasii Kurakin
la source
3

Consultez la bibliothèque final-fs . Il fournit une readdirRecursivefonction:

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
la source
2

Mise en œuvre de la promesse autonome

J'utilise la bibliothèque de promesses when.js dans cet exemple.

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

J'ai inclus un paramètre facultatif includeDirqui inclura les répertoires dans la liste des fichiers s'il est défini sur true.

JayQuerie.com
la source
1

Voici encore une autre implémentation. Aucune des solutions ci-dessus n'a de limiteur, et donc si votre structure de répertoire est grande, elles vont toutes se déborder et éventuellement manquer de ressources.

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

L'utilisation d'une concurrence de 50 fonctionne plutôt bien et est presque aussi rapide que les implémentations plus simples pour les petites structures de répertoires.

Monkey Boson
la source
1

J'ai modifié la réponse basée sur la promesse de Trevor Senior pour travailler avec 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
la source
1

Pour le plaisir, voici une version basée sur le flux qui fonctionne avec la bibliothèque de flux highland.js. Il a été co-écrit par 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
la source
1

Utilisation de Promises ( Q ) pour résoudre ce problème dans un style fonctionnel:

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

Il renvoie une promesse d'un tableau, vous pouvez donc l'utiliser comme:

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

Je dois ajouter la bibliothèque de ponceuse Promise à la liste.

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

Utilisation de 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
la source
0

Parce que tout le monde devrait écrire le sien, j'en ai fait un.

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

SALE

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
la source
0

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

Vous pouvez également utiliser fileNameau lieu de baseNamesi vous avez besoin de l'extension.

Un bonus supplémentaire est qu'il surveillera également les fichiers et rappellera le rappel. Il y a des tonnes d'options de configuration pour le rendre extrêmement flexible.

Je viens de refaire le guardbijou de rubis en utilisant loaddir dans un court laps de temps

Funkodebat
la source
0

Voici ma réponse. J'espère que cela peut aider quelqu'un.

Mon objectif est de faire en sorte que la routine de recherche puisse s'arrêter n'importe où et, pour un fichier trouvé, indique la profondeur relative du chemin d'origine.

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
la source
0

Voici une méthode récursive pour obtenir tous les fichiers, y compris les sous-répertoires.

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
la source
0

Un autre simple et utile

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
la source
Vous supposez que chaque fichier du répertoire racine est un dossier ici.
xechelonx
0

C'est ainsi que j'utilise la fonction nodejs fs.readdir pour rechercher récursivement un répertoire.

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

Supposons que vous ayez un chemin appelé '/ database' dans la racine de vos projets de nœuds. Une fois cette promesse résolue, elle devrait cracher un tableau de chaque fichier sous «/ base de données».

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