рекурсивний пошук у каталозі node.js fs.readdir


268

Будь-які ідеї щодо пошуку в каталозі async за допомогою 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);
});

EDIT: Покращені приклади.


10
Будьте уважні, відповідь "паралельного циклу" від chjj вище має помилку у випадках, коли ходить порожня папка. Виправлення: var pending = list.length; якщо (! очікує) зроблено (нуль, результати); // додайте цей рядок! list.forEach (функція (файл) {...
Василь Даскалопулос

27
file = dir + '/' + file;Це не рекомендується. Вам слід скористатися: var path = require('path'); file = path.resolve(dir, file);
Лейко

7
@onetrickpony, тому що якщо ви скористаєтесь, path.resolve(...)ви отримаєте правильний шлях, будь то в Windows або Unix :) Це означає, що ви отримаєте щось на кшталт C:\\some\\foo\\pathWindows та /some/foo/pathUnix
Leiko

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

5
Для всіх, хто заплутався у !--синтаксисі, було поставлено запитання про це
Tas

145

Цей використовує максимальну кількість нових функцій мовлення, доступних у вузлі 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+ з ще більшою кількістю 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);
}

Зауважте, що починаючи з вузла 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зараз дає нам назад. Це позбавляє нас від необхідності здійснювати додаткові системні дзвінки. Деталі тут
какодер

1
@cacoder Оновлено, щоб включити withFileTypes. Дякую за пораду.
qwtel

в вузлі 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
Мені подобається це рішення, крім вашої нестачі напівколонок!
1313

Це просто. Але також трохи наївно. Може викликати потік stackoverflow, якщо каталог містить посилання на батьківський каталог. Може використовувати lstatзамість цього? Або ж додайте перевірку на рекурсивність, щоб обмежити рівень рекурсивності.
conradkleinespel

14
Подумайте про використання файлу =
изисквати

16
@mpen Напівколони є зайвими
Ally

Це також найкраще працює для мене. Хоча я також додав фільтр для фільтра для конкретного розширення файлу.
Брайан

87

A. Подивіться на модуль файлів . Він має функцію, що називається ходити:

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


9
Це лише UNIX?
Мохсен

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

8
приємна, але не кросова платформа.
f0ster

До речі: Ні, A - це не тільки Unix! Тільки B - це лише Unix. Однак зараз Windows 10 поставляється з підсистемою Linux. Тож навіть B просто працював би у Windows зараз.
Йоганн Філіп Стратхаузен

не було б увімкнено WSL на ПК кінцевих користувачів, щоб він працював у Windows ??
oldboy

38

Інший приємний пакет npm - глобальний .

npm install glob

Він дуже потужний і повинен покривати всі ваші постійні потреби.

Редагувати:

Я насправді не був із задоволенням глобальним , тому створив readdirp .

Я дуже впевнений, що його API робить пошук файлів та каталогів рекурсивним та застосування певних фільтрів дуже простим.

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

npm install readdirp


Кращий модуль на мою думку. І це багато інших проектів, таких як Grunt, Mocha тощо. Та інші 80000 + інші проекти. Просто кажу.
Янік Рошон

29

Я рекомендую використовувати node-glob для виконання цього завдання.

var glob = require( 'glob' );  

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

14

Якщо ви хочете скористатися пакетом npm, гайковий ключ є досить хорошим.

var wrench = require("wrench");

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

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

EDIT (2018):
Усі, хто читав останнім часом: Автор застарів цей пакет у 2015 році:

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


@Domenic, як це робити denodify? Зворотний виклик запускається кілька разів (рекурсивно). Тож використання Q.denodify(wrench.readdirRecursive)повертає лише перший результат.
Onur Yıldırım

1
@ OnurYıldırım так, це не дуже підходить для обіцянок як є. Вам потрібно буде написати щось, що повертає кілька обіцянок, або те, що чекає, поки всі підкаталоги перерахуються перед поверненням обіцянки. Про останнє див. На сайті github.com/kriskowal/q-io#listdirectorytreepath
Доменік

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;

Я створив і «Гісту» . Коментарі вітаються. Я все ще починаю в царині 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) Â {", викликаючи помилку "uncaught SyntaxError: Unexpected token {". Мене наштовхує це питання, і моя негайна реакція - замінити node-dir чимось подібним
Parth

1
@Parth цей коментар не збирається давати вам відповіді. Напишіть нове повне запитання на SO або створіть проблему у сховищі GitHub. Коли ви добре розробимо своє запитання, ви, можливо, зможете вирішити свою проблему, навіть не викладаючи її
Крістіан Вестербек,

1
@ Коментар Парта все ще може бути корисним попередженням для інших, хто розглядає вашу пропозицію як рішення своєї проблеми. Вони, можливо, не шукали відповіді в цьому розділі коментарів :)

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 / wait, це має працювати:

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

Ознайомтеся з бібліотекою фінальних файлів . Він забезпечує 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

Я змінив відповідь на Обіцянку Тревора Старшого, щоб працювати з 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

Використання обіцянок ( 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

Використання bluebird obe.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));

0

Оскільки кожен повинен написати своє, я зробив це.

ходити (dir, cb, endCb) cb (файл) endCb (помилка | null)

Прямо

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.