356 votes

node.js fs.readdir recherche récursive de répertoire

Des idées pour une recherche asynchrone dans un répertoire en utilisant fs.readdir ? Je me rends compte que nous pourrions introduire une récursion et appeler la fonction read directory avec le prochain répertoire à lire, mais j'ai un peu peur que ce ne soit pas asynchrone...

Des idées ? J'ai regardé marche par les nœuds ce qui est génial, mais ne me donne pas seulement les fichiers dans un tableau, comme le fait readdir. Bien que

Vous recherchez des résultats comme...

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

438voto

chjj Points 5676

Il y a essentiellement deux façons d'y parvenir. Dans un environnement asynchrone, vous remarquerez qu'il y a 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, ce qui garantit que chaque itération de la boucle se termine dans l'ordre. Dans une boucle parallèle, toutes les itérations sont lancées en même temps et l'une d'entre elles peut se terminer avant l'autre. Dans ce cas, il est donc probablement préférable d'utiliser une boucle parallèle, car l'ordre dans lequel la marche se termine n'a pas d'importance, du moment qu'elle se termine et renvoie les résultats (sauf si vous les voulez dans l'ordre).

Une boucle parallèle ressemblerait à ceci :

var fs = require('fs');
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 = 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 en série ressemblerait à ceci :

var fs = require('fs');
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 = 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 : Amélioration des exemples.

0 votes

En fait, la version série semble afficher les fichiers dans le répertoire racine... mais pas la version parallèle...

12 votes

Attention, la réponse "boucle parallèle" de chjj ci-dessus a un bug dans les cas où un dossier vide est parcouru. La solution est : var pending = list.length ; if(!pending)done(null, results) ; // ajoutez cette ligne ! list.forEach(function(file) { ...

0 votes

@chjj Peut-être que je ne vois pas les choses aussi clairement que vous, mais dois-je utiliser un sémaphore afin de protéger l'accès à l'information de l'entreprise ? results le réseau ?

133voto

Victor Powell Points 402

Au cas où quelqu'un trouverait cela utile, j'ai également mis en place une synchrone 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()) results = results.concat(walk(file))
        else results.push(file)
    })
    return results
}

61 votes

J'aime cette solution, sauf pour le manque de points-virgules !

0 votes

C'est simple. Mais aussi un peu naïf. Peut provoquer un stackoverflow si un répertoire contient un lien vers un répertoire parent. Peut-être utiliser lstat à la place ? Ou bien ajouter un test de récursivité pour limiter le niveau de récursivité.

16 votes

Pensez à utiliser file = require("path").join(dir,file)

91voto

A. Jetez un coup d'œil à la module fichier . Il possède une fonction appelée walk :

file.walk(start, callback)

Navigue dans une arborescence de fichiers, en appelant la fonction de rappel pour chaque répertoire, en passant les informations suivantes (null, dirPath, dirs, files).

C'est peut-être pour vous ! Et oui, c'est une asynchronisation. Cependant, je pense que vous devriez agréger les chemins complets vous-même, si vous en avez besoin.

B. Une alternative, et même l'une de mes préférées : utiliser la commande unix find pour ça. Pourquoi refaire quelque chose qui a déjà été programmé ? Ce n'est peut-être pas exactement ce dont vous avez besoin, mais ça vaut quand même le coup de vérifier :

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 dispose d'un mécanisme de mise en cache intégré qui rend les recherches ultérieures très rapides, pour autant que seuls quelques dossiers aient été modifiés.

0 votes

J'ai une question à propos de l'exemple B : Pour execFile() ( et exec() ) le stderr et le stdout sont des Buffers donc n'auriez-vous pas besoin de faire stdout.toString.split(" \n ") puisque les tampons ne sont pas des chaînes de caractères ?

9 votes

Agréable, mais pas multiplateforme.

0 votes

Au fait : Non, A n'est pas uniquement Unix ! Seul B est Unix uniquement. Cependant, Windows 10 est maintenant livré avec un sous-système Linux. Donc, même B ne fonctionnerait plus que sous Windows.

45voto

Thorsten Lorenz Points 4419

Un autre paquet npm intéressant est globe terrestre .

npm install glob

Il est très puissant et devrait couvrir tous vos besoins en matière de récurrence.

Edit :

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

Je suis convaincu que son API permet de trouver des fichiers et des répertoires de manière récursive et d'appliquer des filtres spécifiques très facilement.

Lisez son documentation pour avoir une meilleure idée de ce qu'il fait et l'installer via :

npm install readdirp

0 votes

Meilleur module à mon avis. Et il en est de même pour de nombreux autres projets, comme Grunt, Mocha, etc. et d'autres projets des années 80 et plus. Je dis ça comme ça.

3 votes

Pourriez-vous développer les raisons qui vous ont poussé à créer readdrip @Thorsten Lorenz

17voto

Domenic Points 40761

Si vous voulez utiliser un paquet npm, Clé à molette est assez bon.

var wrench = require("wrench");

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

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

0 votes

@Domenic, comment faites-vous denodify ça ? La fonction de rappel est déclenchée plusieurs fois (de manière récursive). Ainsi, en utilisant Q.denodify(wrench.readdirRecursive) ne renvoie que le premier résultat.

1 votes

@OnurYldrm ouais, ce n'est pas un bon ajustement pour les promesses telles quelles. Vous auriez besoin d'é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 point, voir github.com/kriskowal/q-io#listdirectorytreepath

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X