node.js fs.readdir рекурсивный поиск в каталоге


268

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

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

Ищете вывод, как ...

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

Ответы:


379

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

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

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

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

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

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


10
Осторожно, ответ «параллельный цикл» из chjj выше имеет ошибку в случаях, когда проходит пустая папка. Исправление: var pending = list.length; if (! pending) выполнено (null, результаты); // добавить эту строку! list.forEach (функция (файл) {...
Василь Даскалопулос

27
file = dir + '/' + file;Это не рекомендуется. Вы должны использовать: var path = require('path'); file = path.resolve(dir, file);
Лейко

7
@onetrickpony, потому что, если вы используете, path.resolve(...)вы получите правильный путь, будь вы в Windows или Unix :) Это означает, что вы получите что-то вроде C:\\some\\foo\\pathв Windows и /some/foo/pathв системах Unix
Leiko

19
Я не согласился, потому что ваш ответ был великолепен, когда вы впервые написали его еще в 2011 году, но в 2014 году люди используют модули с открытым исходным кодом, сами пишут меньше кода и вносят свой вклад в модули, от которых зависят они и многие другие люди. Например, попробуйте node-dir, чтобы получить именно тот вывод, который требуется @crawf, используя следующую строку кода:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek

5
Для тех, кто не понимает !--синтаксис, был задан вопрос об этом
Tas

147

Этот использует максимальное количество новых, модных слов, доступных в узле 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 = await Promise.all(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/


5
Наименование subdirи subdirsвводит в заблуждение, так как это могут быть файлы (я предлагаю что-то вроде itemInDirили item_in_dirдаже просто itemвместо этого), но это решение кажется чище, чем принятый и гораздо меньше кода. Я также не нахожу это намного более сложным, чем код в принятом ответе. +1
Зельфир Кальцталь

1
Вы можете сделать это еще более круто используя require(fs).promisesпросто и util.promisifyполностью отказаться . Лично я псевдоним fs к fs.promises.
MushinNoShin

2
Мы можем сделать это быстрее с одним небольшим изменением: передавая 2-й аргумент в readdirAKA объекту параметров, как readdir(dir, {withFileTypes: true})это, так что это вернет все элементы с информацией о их типе, поэтому нам вообще не нужно будет вызывать, statчтобы получить информацию, которая readdirтеперь дает нам обратно. Это избавляет нас от необходимости делать дополнительные системные вызовы. Подробности здесь
cacoder

1
@cacoder Обновлено, чтобы включить withFileTypes. Спасибо за чаевые.
2

в узле 10.10+, если заменить return Array.prototype.concat(...files);с let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));вами можем убедиться , что каталоги возвращают «/» , а не «\». Если вы не возражаете против регулярных выражений, вы также можете сделать этоreturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro

106

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

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

60
Мне нравится это решение, за исключением отсутствия точек с запятой!
mpen

Это просто Но тоже немного наивно. Может вызвать переполнение стека, если каталог содержит ссылку на родительский каталог. Может быть, использовать lstatвместо? Или добавьте проверку рекурсивности, чтобы ограничить уровень рекурсивности.
conradkleinespel

14
Подумайте об использовании file = require ("path").
Join

16
@mpen Полуколоны избыточны
союзник

Это также работает лучше всего для меня. Хотя я также добавил фильтр для фильтра для определенного расширения файла.
Брайан

87

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

file.walk (начало, обратный вызов)

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

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

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

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


9
Это только UNIX?
Мохсен

У вас был вопрос о примере B: Для execFile () (и exec ()) stderr и stdout являются буферами ... так что вам не нужно делать stdout.toString.split ("\ n"), так как буферы не являются строками?
Черувим

8
приятно, но не кроссплатформенный.
f0ster

Кстати, нет, A - это не только Unix! Только B - только Unix. Однако Windows 10 теперь поставляется с подсистемой Linux. Так что даже B сейчас будет работать только на Windows.
Иоганн Филипп

Разве WSL не должен быть включен на компьютере конечного пользователя, чтобы он работал в Windows ??
старик

38

Еще один хороший пакет npm - это glob .

npm install glob

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

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

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

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

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

npm install readdirp


Лучший модуль на мой взгляд. И так же, как многие другие проекты, такие как Grunt, Mocha и т. Д. И другие 80'000 + другие проекты. Просто говорю.
Яник Рошон

29

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

var glob = require( 'glob' );  

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

14

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

var wrench = require("wrench");

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

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

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

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


@ Доменик, как ты denodifyэто делаешь ? Обратный вызов запускается несколько раз (рекурсивно). Таким образом, использование Q.denodify(wrench.readdirRecursive)возвращает только первый результат.
Онур Йылдырым

1
@ OnurYıldırım да, это не подходит для обещаний как есть. Вам нужно написать что-то, что возвращает несколько обещаний, или что-то, что ожидает перечисления всех подкаталогов перед возвратом обещания. Что касается последнего, см. Github.com/kriskowal/q-io#listdirectorytreepath
Domenic,

9

Мне понравился ответ из 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;

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


9

С рекурсией

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

3
Я хотел бы предложить не присоединяющиеся строки пути с , /но с использованием pathмодуля: path.join(searchPath, file). Таким образом, вы получите правильные пути независимо от ОС.
Мориц Фридрих

8

Используйте 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);
  })
});

node-dir работал нормально, но когда я использовал его с webpack, у меня возникли некоторые странные проблемы. Â вставляется в функцию readFiles как в «if (err)» {», вызывая« неперехваченную ошибку SyntaxError: Unexpected token {». Я озадачен этим вопросом , и моя непосредственная реакция заключается в замене узла-Dir с чем - то подобным
Parth

1
@ Часть этого комментария не даст вам ответов. Напишите новый полный вопрос о SO или создайте проблему в репозитории GitHub. Если вы тщательно проработаете свой вопрос, вы даже сможете решить свою проблему, даже не
Кристиан

1
Комментарий @ Parth может все еще быть полезным предупреждением для тех, кто рассматривает ваше предложение как решение их проблемы. Возможно, они не искали ответ в этом разделе комментариев :)

4

Я кодировал это недавно, и подумал, что имеет смысл поделиться этим здесь. Код использует асинхронную библиотеку .

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

Вы можете использовать это так:

scan('/some/dir', '.ext', function(err, files) {
  // Do something with files that ends in '.ext'.
  console.log(files);
});

2
Это. Это так аккуратно и просто в использовании. Я выкачал его в модуль, потребовал, и он работает как бутерброд McDream.
Джей

4

Библиотека под названием Filehound является еще одним вариантом. Он будет рекурсивно искать данный каталог (рабочий каталог по умолчанию). Он поддерживает различные фильтры, обратные вызовы, обещания и синхронизируют поиски.

Например, ищите в текущем рабочем каталоге все файлы (используя обратные вызовы):

const Filehound = require('filehound');

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

Или обещания и указание конкретного каталога:

const Filehound = require('filehound');

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

Обратитесь к документации для дальнейших вариантов использования и примеров использования: https://github.com/nspragg/filehound

Отказ от ответственности: я автор.


4

Используя async / await, это должно работать:

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

Вы можете использовать bluebird.Promisify или это:

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

Узел 8+ имеет встроенную функцию Promisify

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


4

асинхронный

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)

Синхронизация

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

Асинхронный читаемый

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)

Примечание: обе версии будут следовать символическим ссылкам (так же, как оригинал fs.readdir)


3

Проверьте библиотеку final-fs . Это обеспечивает readdirRecursiveфункцию:

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

2

Автономная реализация обещаний

В этом примере я использую библиотеку обещаний 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);
});

Я включил необязательный параметр, includeDirкоторый будет включать каталоги в список файлов, если установлен в true.



1

Вот еще одна реализация. Ни одно из приведенных выше решений не имеет каких-либо ограничений, и поэтому, если ваша структура каталогов велика, все они будут работать с перебоями и в конечном итоге исчерпать ресурсы.

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

Использование параллелизма 50 работает довольно хорошо и почти так же быстро, как и простые реализации для небольших структур каталогов.



1

Я изменил ответ Trevor Senior's Promise для работы с 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); {
});

1

Для интереса, это потоковая версия, которая работает с потоковой библиотекой highland.js. В соавторстве с Виктором Ву.

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

1

Используя Promises ( Q ), чтобы решить это в функциональном стиле:

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

Он возвращает обещание массива, поэтому вы можете использовать его как:

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

1

Я должен добавить библиотеку Sander на основе Promise в список.

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

1

Использование синей птицы обещаем.

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

0

Поскольку каждый должен написать свое, я сделал один.

walk (dir, cb, endCb) cb (файл) endCb (err | null)

DIRTY

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

  });
}

0

проверить 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); }
  })

Вы можете использовать fileNameвместо, baseNameесли вам нужно расширение, а также.

Дополнительным бонусом является то, что он будет также просматривать файлы и снова вызывать обратный вызов. Есть множество вариантов конфигурации, чтобы сделать его чрезвычайно гибким.

Я просто переделал guardдрагоценный камень из рубина с помощью loaddir в течение короткого времени


0

Это мой ответ. Надеюсь, это может кому-нибудь помочь.

Моя задача - сделать так, чтобы процедура поиска могла остановиться где угодно, а для найденного файла указывается относительная глубина по отношению к исходному пути.

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

0

Вот рекурсивный метод получения всех файлов, включая подкаталоги.

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

0

Еще один простой и полезный

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

Вы предполагаете, что каждый файл в корневом каталоге является папкой здесь.
xechelonx

0

Вот как я использую функцию nodejs fs.readdir для рекурсивного поиска в каталоге.

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

Допустим, у вас есть путь с именем / database в корневом каталоге ваших проектов. Как только это обещание будет выполнено, оно должно выложить массив каждого файла в «/ database».

readdirRecursivePromise('database').then(out => {
    console.log(flatten(out));
}).catch(err => {
    console.log(err);
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.