node.js fs.readdir рекурсивный поиск в каталоге - PullRequest
229 голосов
/ 29 апреля 2011

Есть идеи по поиску в асинхронном каталоге с использованием fs.readdir?Я понимаю, что мы могли бы ввести рекурсию и вызвать функцию чтения каталога со следующим каталогом для чтения, но меня немного беспокоит, что он не будет асинхронным ...

Есть идеи?Я посмотрел на node-walk , который великолепен, но не дает мне только файлы в массиве, как readdir.Хотя

Ищем вывод вроде ...

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

Ответы [ 33 ]

356 голосов
/ 29 апреля 2011

Есть два основных способа сделать это.В асинхронной среде вы заметите, что есть два вида циклов: последовательный и параллельный.Последовательный цикл ожидает завершения одной итерации, прежде чем перейти к следующей итерации - это гарантирует, что каждая итерация цикла завершается по порядку.В параллельном цикле все итерации запускаются одновременно, и одну можно завершить раньше другой, однако это намного быстрее, чем последовательный цикл.Так что в этом случае, вероятно, лучше использовать параллельный цикл, потому что не имеет значения, в каком порядке выполняется обход, до тех пор, пока он завершает и возвращает результаты (если вы не хотите, чтобы они были в порядке).

Параллельный цикл будет выглядеть так:

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

Последовательный цикл будет выглядеть следующим образом:

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

И для проверки его в вашем домашнем каталоге (ВНИМАНИЕ: список результатовбудет огромным, если у вас есть много вещей в вашем домашнем каталоге):

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

РЕДАКТИРОВАТЬ: Улучшенные примеры.

91 голосов
/ 22 мая 2013

На всякий случай, если кому-то это будет полезно, я также собрал синхронную версию.

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

Подсказка: использовать меньше ресурсов при фильтрации. Фильтруйте внутри этой функции. Например. Замените results.push(file); на приведенный ниже код. Отрегулируйте при необходимости:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);
85 голосов
/ 15 июня 2011

A. Посмотрите на файл модуля . У него есть функция, которая называется walk:

file.walk (запуск, обратный вызов)

Перемещение по дереву файлов, обратный вызов для каждого каталога, передача (null, dirPath, dirs, файлы).

Это может быть для вас! И да, это асинхронно. Тем не менее, я думаю, что вам придется объединять полный путь самостоятельно, если вам они нужны.

B. Альтернатива, и даже один из моих любимых: для этого используйте unix find. Зачем опять что-то, что уже запрограммировано? Может быть, не совсем то, что вам нужно, но все же стоит проверить:

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 имеет хороший встроенный механизм кэширования, который делает последующие поиски очень быстрыми, если изменилось только несколько папок.

69 голосов
/ 16 июля 2017

В этом варианте используется максимальное количество новых, модных слов, доступных в узле 8, включая обещания, использование / обещание, деструктуризацию, асинхронное ожидание, отображение + уменьшение и многое другое, что заставляет ваших коллег почесывать голову при попытке выяснить, что происходит.

Узел 8 +

Нет внешних зависимостей.

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

Использование

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

Узел 10.10 +

Обновлено для узла 10+ с еще большим количеством ударов:

const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  const files = dirents.map((dirent) => {
    const res = resolve(dir, dirent.name);
    return dirent.isDirectory() ? getFiles(res) : res;
  });
  return Array.prototype.concat(...files);
}

Обратите внимание, что начиная с узла 11.15.0, вы можете использовать files.flat() вместо Array.prototype.concat(...files) для выравнивания массива файлов.

Узел 11 +

Если вы хотите полностью поднять голову всем, вы можете использовать следующую версию, используя асинхронные итераторы . Помимо того, что он действительно крут, он также позволяет потребителям получать результаты по одному, что делает его более подходящим для действительно больших каталогов.

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

Использование изменилось, потому что тип возвращаемого значения теперь асинхронный итератор вместо обещания

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

На случай, если кому-то интересно, я написал больше об асинхронных итераторах здесь: https://qwtel.com/posts/software/async-generators-in-the-wild/

38 голосов
/ 01 июня 2012

Еще один приятный пакет npm: glob .

npm install glob

Он очень мощный и должен охватывать все ваши текущие потребности.

Редактировать:

Я на самом деле не был полностью доволен glob, поэтому я создал readdirp .

Я очень уверен, что его API делает поиск файлов и каталогов рекурсивным и применяетспециальные фильтры очень просто.

Прочитайте документацию , чтобы получить лучшее представление о том, что он делает, и установите с помощью:

npm install readdirp

26 голосов
/ 11 марта 2015

Я рекомендую использовать node-glob для выполнения этой задачи.

var glob = require( 'glob' );  

glob( 'dirname/**/*.js', function( err, files ) {
  console.log( files );
});
15 голосов
/ 24 апреля 2012

Если вы хотите использовать пакет npm, ключ довольно хорош.

var wrench = require("wrench");

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

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

РЕДАКТИРОВАТЬ (2018):
Любой, кто читает в последнее время:Автор не поддерживает этот пакет в 2015 году:

wrench.js устарел и не обновлялся в течение достаточно долгого времени. Я настоятельно рекомендую использовать fs-extra для выполнения любых дополнительных операций с файловой системой.

9 голосов
/ 14 сентября 2012

Мне понравился ответ из chjj выше и я бы не смог создать мою версию параллельного цикла без этого запуска.

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;

Iсоздал Суть .Комментарии приветствуются.Я все еще начинаю в сфере NodeJS, поэтому я надеюсь узнать больше.

8 голосов
/ 14 мая 2014

Используйте node-dir , чтобы получить именно тот результат, который вам нравится

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);
  })
});
7 голосов
/ 20 апреля 2016

с рекурсией

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

Вызов

getFiles(path, files)
console.log(files) // will log all files in directory
...