node.js fs.readdir tìm kiếm thư mục đệ quy


268

Bất kỳ ý tưởng về tìm kiếm thư mục async bằng fs.readdir? Tôi nhận ra rằng chúng tôi có thể giới thiệu đệ quy và gọi chức năng thư mục đọc với thư mục tiếp theo để đọc, nhưng tôi hơi lo lắng về việc nó không được đồng bộ hóa ...

Có ý kiến ​​gì không? Tôi đã xem nút đi bộ rất tuyệt, nhưng không cung cấp cho tôi các tệp trong một mảng, giống như readdir. Mặc du

Tìm kiếm đầu ra như ...

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

Câu trả lời:


379

Về cơ bản có hai cách để thực hiện điều này. Trong môi trường không đồng bộ, bạn sẽ nhận thấy rằng có hai loại vòng: nối tiếp và song song. Một vòng lặp nối tiếp chờ đợi một lần lặp hoàn thành trước khi nó chuyển sang lần lặp tiếp theo - điều này đảm bảo rằng mỗi lần lặp của vòng lặp hoàn thành theo thứ tự. Trong một vòng lặp song song, tất cả các lần lặp được bắt đầu cùng một lúc và người ta có thể hoàn thành trước một vòng lặp khác, tuy nhiên, nó nhanh hơn nhiều so với vòng lặp nối tiếp. Vì vậy, trong trường hợp này, có lẽ tốt hơn là sử dụng một vòng lặp song song bởi vì việc đi bộ hoàn thành theo thứ tự nào, miễn là nó hoàn thành và trả về kết quả (trừ khi bạn muốn chúng theo thứ tự).

Một vòng lặp song song sẽ trông như thế này:

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

Một vòng lặp nối tiếp sẽ trông như thế này:

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

Và để kiểm tra nó trên thư mục chính của bạn (CẢNH BÁO: danh sách kết quả sẽ rất lớn nếu bạn có nhiều nội dung trong thư mục chính của mình):

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

EDIT: Các ví dụ cải tiến.


10
Coi chừng, câu trả lời "vòng lặp song song" từ chjj ở trên có lỗi trong trường hợp khi một thư mục trống được chuyển đi. Cách khắc phục là: var chờ đợi = list.length; if (! chờ xử lý) xong (null, kết quả); // thêm dòng này! list.forEach (chức năng (tập tin) {...
Vasil Daskalopoulos

27
file = dir + '/' + file;Điều này không được khuyến khích. Bạn nên sử dụng: var path = require('path'); file = path.resolve(dir, file);
Leiko

7
@onetrickpony bởi vì nếu bạn sử dụng, path.resolve(...)bạn sẽ có một đường dẫn phù hợp cho dù bạn đang ở trên Windows hay Unix :) Có nghĩa là bạn sẽ nhận được một cái gì đó giống như C:\\some\\foo\\pathtrên Windows và /some/foo/pathtrên các hệ thống Unix
Leiko 22/12/13

19
Tôi đã từ chối vì câu trả lời của bạn rất hay khi bạn viết lại lần đầu vào năm 2011, nhưng năm 2014 mọi người sử dụng các mô-đun nguồn mở và tự viết ít mã hơn và đóng góp cho các mô-đun mà họ và rất nhiều người khác phụ thuộc. Ví dụ: hãy thử nút-dir để có được chính xác đầu ra theo yêu cầu của @crawf bằng cách sử dụng dòng mã này:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek

5
Đối với bất kỳ ai nhầm lẫn về !--cú pháp, một câu hỏi đã được hỏi về nó
TAS

146

Ứng dụng này sử dụng số lượng tối đa các tính năng mới, từ thông dụng có sẵn trong nút 8, bao gồm Hứa, tận dụng / quảng cáo, phá hủy, chờ đợi, bản đồ + giảm và hơn thế nữa, khiến đồng nghiệp của bạn gãi đầu khi họ cố gắng tìm ra điều gì đang diễn ra

Nút 8+

Không phụ thuộc bên ngoài.

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

Sử dụng

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

Nút 10.10+

Đã cập nhật cho nút 10+ với nhiều whizbang hơn:

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

Lưu ý rằng bắt đầu với nút 11.15.0, bạn có thể sử dụng files.flat()thay vì Array.prototype.concat(...files)làm phẳng mảng tệp.

Nút 11+

Nếu bạn muốn thổi bay hoàn toàn đầu của mọi người, bạn có thể sử dụng phiên bản sau bằng cách sử dụng các trình vòng lặp async . Ngoài việc thực sự tuyệt vời, nó cũng cho phép người tiêu dùng rút ra kết quả cùng một lúc, làm cho nó phù hợp hơn cho các thư mục thực sự lớn.

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

Cách sử dụng đã thay đổi vì loại trả về bây giờ là trình lặp không đồng bộ thay vì lời hứa

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

Trong trường hợp ai đó quan tâm, tôi đã viết thêm về trình lặp async tại đây: https://qwtel.com/posts/software/async-generators-in-the-wild/


5
Việc đặt tên của subdirsubdirsđược gây hiểu lầm, như những người có thể thực sự file (Tôi đề nghị một cái gì đó giống như itemInDirhoặc item_in_dirhoặc thậm chí chỉ đơn giản là itemthay thế.), Nhưng giải pháp này cảm thấy sạch hơn so với cái được chấp nhận và là ít hơn nhiều mã. Tôi cũng không thấy nó phức tạp hơn nhiều so với mã trong câu trả lời được chấp nhận. +1
Zelphir Kaltstahl

1
Bạn có thể làm cho điều này thậm chí nhiều whizbang hơn bằng cách sử dụng require(fs).promisesvà chỉ cần thả util.promisifyhoàn toàn. Cá nhân tôi bí danh fs để fs.promises.
MushinNoShin

2
Chúng ta có thể thực hiện việc này nhanh hơn với một thay đổi nhỏ: chuyển đối số thứ 2 cho readdirAKA đối tượng tùy chọn như thế, readdir(dir, {withFileTypes: true})điều này sẽ trả về tất cả các mục có thông tin loại của chúng, vì vậy chúng ta sẽ không cần phải gọi statđể có được thông tin readdirhiện cung cấp cho chúng ta trở lại. Điều này giúp chúng tôi không cần phải thực hiện các cuộc gọi sys bổ sung. Chi tiết tại đây
cacoder

1
@cacoder Cập nhật để bao gồm withFileTypes. Cảm ơn vì tiền hỗ trợ.
qwtel

trong nút 10.10+, nếu bạn thay thế return Array.prototype.concat(...files);bằng let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));bạn có thể đảm bảo các thư mục trả về "/" chứ không phải "\". Nếu bạn không nhớ regex, bạn cũng có thể làmreturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro

106

Chỉ trong trường hợp bất cứ ai thấy nó hữu ích, tôi cũng đưa ra một phiên bản đồng bộ .

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

Mẹo: Để sử dụng ít tài nguyên hơn khi lọc. Lọc trong chính chức năng này. Ví dụ: Thay thế results.push(file);bằng mã dưới đây. Điều chỉnh theo yêu cầu:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);

60
Tôi thích giải pháp này, ngoại trừ việc bạn thiếu dấu hai chấm!
mở

Cái này đơn giản. Nhưng cũng hơi ngây thơ. Có thể gây ra stackoverflow nếu một thư mục chứa liên kết đến thư mục cha. Có thể sử dụng lstatthay thế? Hoặc nếu không, thêm kiểm tra đệ quy để giới hạn mức đệ quy.
conradkleinespel

14
Xem xét sử dụng tệp = Yêu cầu ("đường dẫn"). Tham gia (dir, tệp)
mkamioner

16
@mpen Semi-colons là dự phòng
minh

Điều này cũng làm việc tốt nhất cho tôi. Mặc dù tôi cũng đã thêm một bộ lọc để lọc cho một phần mở rộng tập tin cụ thể.
Brian

87

A. Hãy xem mô-đun tập tin . Nó có chức năng gọi là walk:

file.walk (bắt đầu, gọi lại)

Điều hướng một cây tệp, gọi lại cho mỗi thư mục, đi vào (null, dirPath, dirs, tệp).

Điều này có thể dành cho bạn! Và vâng, nó không đồng bộ. Tuy nhiên, tôi nghĩ bạn sẽ phải tự mình tổng hợp đường dẫn đầy đủ, nếu bạn cần chúng.

B. Một cách khác, và thậm chí một trong những mục yêu thích của tôi: sử dụng unix findcho điều đó. Tại sao làm một cái gì đó một lần nữa, mà đã được lập trình? Có thể không chính xác những gì bạn cần, nhưng vẫn đáng để kiểm tra:

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 có một cơ chế bộ nhớ đệm tích hợp đẹp mắt giúp cho các tìm kiếm tiếp theo rất nhanh, miễn là chỉ có một vài thư mục đã thay đổi.


9
Đây chỉ là UNIX?
Mohsen

Có một câu hỏi về ví dụ B: Đối với execFile () (và exec ()) stderr và stdout là Buffers .. vì vậy bạn không cần phải thực hiện stdout.toString.split ("\ n") vì Buffers không phải là String?
Cheruvim

8
đẹp, nhưng không đa nền tảng.
f0ster

Nhân tiện: Không, A không chỉ là Unix! Chỉ có B là Unix mà thôi. Tuy nhiên, Windows 10 bây giờ đi kèm với một hệ thống con Linux. Vì vậy, ngay cả B sẽ chỉ hoạt động trên Windows ngày nay.
Johann Philipp Strathausen

WSL có phải được bật trên PC người dùng cuối để nó hoạt động trên Windows không ??
oldboy

38

Một gói npm tốt đẹp khác là trên toàn cầu .

npm install glob

Nó rất mạnh mẽ và sẽ đáp ứng mọi nhu cầu đệ quy của bạn.

Biên tập:

Tôi thực sự không hoàn toàn hài lòng với toàn cầu, vì vậy tôi đã tạo ra readdirp .

Tôi rất tự tin rằng API của nó giúp việc tìm kiếm các tệp và thư mục theo cách đệ quy và áp dụng các bộ lọc cụ thể rất dễ dàng.

Đọc qua tài liệu của nó để có ý tưởng tốt hơn về những gì nó làm và cài đặt qua:

npm install readdirp


Mô-đun tốt nhất theo ý kiến ​​của tôi. Và cũng giống như nhiều dự án khác, như Grunt, Mocha, v.v. và 80'000 + dự án khác. Chỉ cần nói.
Yanick Rochon

29

Tôi khuyên bạn nên sử dụng nút-global để hoàn thành nhiệm vụ đó.

var glob = require( 'glob' );  

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

14

Nếu bạn muốn sử dụng gói npm, cờ lê là khá tốt.

var wrench = require("wrench");

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

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

EDIT (2018):
Bất kỳ ai đọc qua trong thời gian gần đây: Tác giả đã từ chối gói này vào năm 2015:

wbler.js không được dùng nữa và đã không được cập nhật trong một thời gian. Tôi thực sự khuyên bạn nên sử dụng fs-Extra để thực hiện bất kỳ hoạt động hệ thống tập tin bổ sung nào.


@Domenic, làm thế nào để bạn làm denodifyđiều này? Gọi lại được sa thải nhiều lần (đệ quy). Vì vậy, sử dụng Q.denodify(wrench.readdirRecursive)chỉ trả về kết quả đầu tiên.
Onur Yıldırım

1
@ OnurYıldırım yeah, điều này không phù hợp với những lời hứa như hiện tại. Bạn sẽ cần phải viết một cái gì đó trả về nhiều lời hứa hoặc một cái gì đó chờ cho đến khi tất cả các thư mục con được liệt kê trước khi trả lại một lời hứa. Để biết thêm thông tin, hãy xem github.com/kriskowal/q-io#listdirectorytreepath
Domenic

9

Tôi thích câu trả lời từ chjj ở trên và sẽ không thể tạo phiên bản vòng lặp song song của tôi mà không bắt đầu.

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;

Tôi đã tạo ra một Gist là tốt. Bình luận hoan nghênh. Tôi vẫn đang bắt đầu trong vương quốc NodeJS vì vậy đó là một cách tôi hy vọng để tìm hiểu thêm.


9

Với đệ quy

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

Gọi điện thoại

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

3
Tôi khuyên bạn không nên tham gia chuỗi đường dẫn với /nhưng sử dụng pathmô-đun : path.join(searchPath, file). Bằng cách đó, bạn sẽ có được các đường dẫn chính xác độc lập với HĐH.
Moritz Friedrich

8

Sử dụng nút-dir để tạo chính xác đầu ra mà bạn thích

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 đã hoạt động tốt, nhưng khi tôi sử dụng nó với webpack tôi gặp một số vấn đề kỳ lạ. Một  được chèn vào hàm readFiles như trong "if (err)  {" gây ra lỗi "chưa được đánh dấu SyntaxError: Mã thông báo không mong đợi {". Tôi bị bối rối bởi vấn đề này và phản ứng tức thời của tôi là thay thế nút-dir bằng một cái gì đó tương tự
Parth

1
@Parth bình luận này sẽ không cung cấp cho bạn câu trả lời. Viết một câu hỏi đầy đủ mới về SO hoặc tạo một vấn đề tại kho GitHub. Khi bạn giải thích tốt câu hỏi của mình, bạn thậm chí có thể giải quyết vấn đề của mình mà không cần phải đăng nó
Christiaan Westerbeek

1
Nhận xét của @ Parth vẫn có thể là một cảnh báo hữu ích cho những người khác đang xem đề xuất của bạn là giải pháp cho vấn đề của họ. Họ có thể đã không tìm kiếm câu trả lời trong phần bình luận này :)

4

Tôi đã mã hóa điều này gần đây và nghĩ rằng sẽ rất hợp lý khi chia sẻ điều này ở đây. Mã này sử dụng thư viện 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);
    });
  });
};

Bạn có thể sử dụng nó như thế này:

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

2
Điều này. Điều này rất gọn gàng và đơn giản để sử dụng. Tôi đã bơm nó ra một mô-đun, yêu cầu nó và nó hoạt động như một bánh sandwich mcdream.
Jay

4

Một thư viện có tên Filehound là một lựa chọn khác. Nó sẽ đệ quy tìm kiếm một thư mục nhất định (thư mục làm việc theo mặc định). Nó hỗ trợ nhiều bộ lọc, cuộc gọi lại, lời hứa và tìm kiếm đồng bộ hóa.

Ví dụ: tìm kiếm thư mục làm việc hiện tại cho tất cả các tệp (sử dụng hàm gọi lại):

const Filehound = require('filehound');

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

Hoặc hứa hẹn và chỉ định một thư mục cụ thể:

const Filehound = require('filehound');

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

Tham khảo tài liệu để biết thêm các trường hợp sử dụng và ví dụ về việc sử dụng: https://github.com/nspragg/filehound

Tuyên bố miễn trừ trách nhiệm: Tôi là tác giả.


4

Sử dụng async / await, điều này sẽ hoạt động:

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

Bạn có thể sử dụng bluebird.Promisify hoặc này:

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

Nút 8+ đã tích hợp sẵn

Xem câu trả lời khác của tôi cho cách tiếp cận máy phát điện có thể cho kết quả nhanh hơn.


4

Không đồng bộ

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)

Đồng bộ hóa

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

Không đồng bộ có thể đọc được

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)

Lưu ý: cả hai phiên bản sẽ đi theo liên kết tượng trưng (giống như bản gốc fs.readdir)


3

Kiểm tra thư viện fs cuối cùng . Nó cung cấp một readdirRecursivechức năng:

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

Thực hiện lời hứa độc lập

Tôi đang sử dụng thư viện lời hứa khi.js trong ví dụ này.

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

Tôi đã bao gồm một tham số tùy chọn includeDirsẽ bao gồm các thư mục trong danh sách tệp nếu được đặt thành true.



1

Đây là một thực hiện khác . Không có giải pháp nào ở trên có bất kỳ bộ giới hạn nào, và vì vậy nếu cấu trúc thư mục của bạn lớn, tất cả chúng sẽ bị phá hủy và cuối cùng hết tài nguyên.

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

Sử dụng đồng thời 50 hoạt động khá tốt và gần như nhanh như việc triển khai đơn giản hơn cho các cấu trúc thư mục nhỏ.



1

Tôi đã sửa đổi câu trả lời dựa trên Promise của Trevor Senior để làm việc với 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

Để giải trí, đây là phiên bản dựa trên luồng hoạt động với thư viện luồng highland.js. Nó được đồng tác giả bởi Victor Vũ.

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

Sử dụng Promise ( Q ) để giải quyết vấn đề này theo kiểu Chức năng:

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

Nó trả về một lời hứa của một mảng, vì vậy bạn có thể sử dụng nó như:

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

1

Tôi phải thêm thư viện sander dựa trên Promise vào danh sách.

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

1

Sử dụng bluebird hứa.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

Bởi vì mọi người nên viết riêng của mình, tôi đã làm một.

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

DƠ BẨN

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

kiểm tra 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); }
  })

Bạn có thể sử dụng fileNamethay vìbaseName nếu bạn cần phần mở rộng là tốt.

Một phần thưởng bổ sung là nó cũng sẽ xem các tập tin và gọi lại cuộc gọi lại. Có hàng tấn tùy chọn cấu hình để làm cho nó cực kỳ linh hoạt.

Tôi chỉ cần làm lại guardđá quý từ ruby ​​bằng cách sử dụng loaddir trong một thời gian ngắn


0

Đây là câu trả lời của tôi. Hy vọng nó có thể giúp ai đó.

Trọng tâm của tôi là làm cho thói quen tìm kiếm có thể dừng ở bất cứ đâu và đối với một tệp được tìm thấy, cho biết độ sâu tương đối của đường dẫn gốc.

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

Đây là một phương pháp đệ quy để nhận tất cả các tệp bao gồm các thư mục con.

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

Một cách đơn giản và hữu ích khác

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

Bạn đang giả sử rằng mọi tệp trong thư mục gốc là một thư mục ở đây.
xechelonx

0

Đây là cách tôi sử dụng hàm nodejs fs.readdir để tìm kiếm đệ quy một thư mục.

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

Giả sử bạn có một đường dẫn gọi là '/ cơ sở dữ liệu' trong gốc dự án nút của bạn. Khi lời hứa này được giải quyết, nó sẽ nhổ ra một mảng của mọi tệp trong '/ cơ sở dữ liệu'.

readdirRecursivePromise('database').then(out => {
    console.log(flatten(out));
}).catch(err => {
    console.log(err);
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.