node.js fs.readdir ค้นหาไดเรกทอรีแบบเรียกซ้ำ


268

มีแนวคิดใดบ้างในการค้นหาไดเรกทอรี async โดยใช้ fs.readdir ฉันรู้ว่าเราสามารถแนะนำการเรียกซ้ำและเรียกใช้ฟังก์ชั่นการอ่านไดเรกทอรีที่มีไดเรกทอรีต่อไปที่จะอ่าน แต่ฉันกังวลเล็กน้อยเกี่ยวกับมันไม่ได้เป็น async ...

ความคิดใด ๆ ฉันดูที่โหนดเดินซึ่งยอดเยี่ยม แต่ไม่ได้ให้เฉพาะไฟล์ในอาร์เรย์เหมือน 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
ระวังคำตอบ "parallel loop" จาก chjj ด้านบนมีข้อผิดพลาดในกรณีที่โฟลเดอร์ว่างกำลังเดิน การแก้ไขคือ: var pending = list.length; ถ้า (! อยู่ระหว่างดำเนินการ) (null, ผลลัพธ์) // เพิ่มบรรทัดนี้! list.forEach (ฟังก์ชั่น (ไฟล์) {...
Vasil Daskalopoulos

27
file = dir + '/' + file;ไม่แนะนำ คุณควรใช้: var path = require('path'); file = path.resolve(dir, file);
Leiko

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

145

อันนี้ใช้จำนวนสูงสุดของฟีเจอร์ buzzwordy ใหม่ที่มีอยู่ในโหนด 8 รวมถึง Promises, util / promisify, destructuring, async-await, map + ลดและอื่น ๆ ทำให้เพื่อนร่วมงานของคุณเกาหัวขณะที่พวกเขาพยายามหาว่าอะไร กำลังเกิดขึ้น

โหนด 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+

หากคุณต้องการที่จะระเบิดหัวของทุกคนขึ้นอย่างสมบูรณ์คุณสามารถใช้รุ่นต่อไปใช้iterators async นอกเหนือจากความเจ๋งจริงๆแล้วมันยังช่วยให้ผู้บริโภคสามารถดึงผลลัพธ์ออกมาได้ครั้งละหนึ่งครั้งทำให้เหมาะสำหรับไดเรกทอรีที่มีขนาดใหญ่ขึ้น

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 แทนที่จะเป็นสัญญา

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

ในกรณีที่ใครบางคนมีความสนใจฉันได้เขียนเพิ่มเติมเกี่ยวกับ async iterators ที่นี่: https://qwtel.com/posts/software/async-generators-in-the-wild/


5
การตั้งชื่อของsubdirและsubdirsจะทำให้เข้าใจผิดเป็นผู้จริงอาจจะไฟล์ (ผมขอแนะนำสิ่งที่ต้องการitemInDirหรือitem_in_dirหรือแม้กระทั่งเพียงแค่itemแทน.) แต่การแก้ปัญหานี้ให้ความรู้สึกสะอาดกว่าได้รับการยอมรับและเป็นรหัสน้อยมาก ฉันยังไม่พบว่ามันซับซ้อนเกินกว่ารหัสในคำตอบที่ยอมรับได้ +1
Zelphir Kaltstahl

1
คุณสามารถทำให้ยิ่งน่าตื่นตาตื่นใจมากขึ้นโดยใช้require(fs).promisesและเพียงวางutil.promisifyสมบูรณ์ ส่วนตัวฉันนามแฝง fs เพื่อ fs.promises
MushinNoShin

2
เราสามารถทำให้สิ่งนี้เร็วขึ้นด้วยการเปลี่ยนแปลงเพียงครั้งเดียว: ส่งผ่านอาร์กิวเมนต์ที่สองไปยังreaddirAKA ซึ่งเป็นอ็อพชั่นอ็อพชั่นเช่นreaddir(dir, {withFileTypes: true})นี้จะส่งคืนไอเท็มทั้งหมดด้วยข้อมูลประเภทดังนั้นเราไม่จำเป็นต้องโทรstatเลยเพื่อรับข้อมูลที่readdirให้ตอนนี้ กลับ. สิ่งนี้ช่วยให้เราไม่จำเป็นต้องโทรออกระบบเพิ่มเติม รายละเอียดที่นี่
cacoder

1
@cacoder withFileTypesปรับปรุงเพื่อรวม ขอบคุณสำหรับทิป.
qwtel

ในโหนด 10.10 ขึ้นไปถ้าคุณแทนที่return Array.prototype.concat(...files);ด้วยlet result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));คุณสามารถตรวจสอบ dirs กลับเป็น "/" และไม่ได้เป็น "\" หากคุณไม่สนใจ regex คุณสามารถทำได้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
ลองใช้ไฟล์ = require ("path") เข้าร่วม (dir, file)
mkamioner

16
@mpen Semi-colons ซ้ำซ้อน
Ally

สิ่งนี้ใช้ได้ดีที่สุดสำหรับฉัน แม้ว่าฉันจะเพิ่มตัวกรองเพื่อกรองส่วนขยายไฟล์เฉพาะ
Brian

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 นี้เท่านั้นหรือไม่
Mohsen

มีคำถามเกี่ยวกับตัวอย่าง B: สำหรับ execFile () (และ exec ()) stderr และ stdout เป็นบัฟเฟอร์ .. ดังนั้นคุณไม่จำเป็นต้องทำ stdout.toString.split ("\ n") เนื่องจากบัฟเฟอร์ไม่ใช่ Strings?
Cheruvim

8
ดี แต่ไม่ข้ามแพลตฟอร์ม
f0ster

โดยวิธีการ: ไม่ A ไม่ได้เป็น Unix เท่านั้น! เฉพาะ B คือ Unix เท่านั้น อย่างไรก็ตามตอนนี้ Windows 10 มาพร้อมกับระบบย่อย Linux ดังนั้นแม้ B จะเพิ่งทำงานบน Windows ทุกวันนี้
โยฮันน์ฟิลิปป์สแตร ธ โทเซน

จะไม่เปิดใช้งาน WSL บนพีซีของผู้ใช้ปลายทางเพื่อให้ทำงานบน Windows ได้หรือไม่
oldboy

38

แพคเกจ NPM ที่ดีก็คือglob

npm install glob

มันมีประสิทธิภาพมากและควรครอบคลุมทุกความต้องการของคุณซ้ำ

แก้ไข:

ที่จริงผมก็ไม่ได้มีความสุขอย่างสมบูรณ์แบบด้วย glob ดังนั้นฉันสร้างreaddirp

ฉันมั่นใจมากว่า API ทำให้การค้นหาไฟล์และไดเรกทอรีซ้ำและใช้ตัวกรองเฉพาะง่ายมาก

อ่านเอกสารของมันเพื่อทำความเข้าใจเกี่ยวกับสิ่งที่มันทำและติดตั้งผ่าน:

npm install readdirp


โมดูลที่ดีที่สุดในความคิดของฉัน และก็เหมือนโครงการอื่น ๆ อีกมากมายเช่น Grunt, Mocha เป็นต้นและโครงการอื่น ๆ 80,000 โครงการ แค่พูด.
Yanick Rochon

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

แก้ไข (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
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;

ฉันสร้างส่วนสำคัญเช่นกัน ยินดีต้อนรับความคิดเห็น ฉันยังคงเริ่มต้นในอาณาจักรของ 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)ด้วยวิธีนี้คุณจะได้รับเส้นทางที่ถูกต้องโดยไม่ขึ้นกับระบบปฏิบัติการ
Moritz Friedrich

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: โทเค็นที่ไม่คาดคิด {" ฉันนิ่งงันโดยปัญหานี้และปฏิกิริยาทันทีของฉันคือการแทนที่ node-dir ด้วยสิ่งที่คล้ายกัน
Parth

1
@Parth ความคิดเห็นนี้จะไม่ให้คำตอบกับคุณ เขียนคำถามเต็มใหม่เกี่ยวกับ SO หรือสร้างปัญหาที่พื้นที่เก็บข้อมูล GitHub เมื่อคุณอธิบายคำถามของคุณได้ดีคุณอาจจะสามารถแก้ปัญหาของคุณได้โดยไม่ต้องโพสต์มัน
Christiaan Westerbeek

1
ความคิดเห็นของ @ Parth อาจเป็นคำเตือนที่เป็นประโยชน์สำหรับผู้อื่นที่กำลังพิจารณาข้อเสนอแนะของคุณเป็นวิธีแก้ปัญหาของพวกเขา พวกเขาอาจไม่ได้มองหาคำตอบในส่วนความเห็นนี้ :)

4

ฉันเพิ่งเขียนโค้ดนี้เมื่อไม่นานมานี้และคิดว่ามันสมเหตุสมผลที่จะแบ่งปันสิ่งนี้ที่นี่ ทำให้รหัสใช้ของห้องสมุด 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);
    });
  });
};

คุณสามารถใช้สิ่งนี้:

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

2
นี้. นี่เป็นระเบียบเรียบร้อยและใช้งานง่ายมาก ฉันสูบมันออกเป็นโมดูลต้องการมันและมันก็ทำงานได้เหมือนแซนด์วิช mcdream
Jay

4

ห้องสมุดชื่อFilehoundเป็นอีกตัวเลือกหนึ่ง มันจะค้นหาไดเรกทอรีที่กำหนดซ้ำ (ไดเรกทอรีทำงานโดยค่าเริ่มต้น) สนับสนุนตัวกรองต่าง ๆ callbacks สัญญาและซิงค์การค้นหา

ตัวอย่างเช่นค้นหาไดเร็กทอรีการทำงานปัจจุบันสำหรับไฟล์ทั้งหมด (ใช้ callbacks):

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

คุณสามารถใช้บลูเบิร์ดให้สัญญาหรือสิ่งนี้:

/**
 * 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

async

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

Async สามารถอ่านได้

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)

หมายเหตุ:ทั้งสองเวอร์ชันจะติดตาม symlink (เหมือนกับต้นฉบับfs.readdir)



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

เราได้รวมพารามิเตอร์ซึ่งจะรวมถึงไดเรกทอรีในแฟ้มรายชื่อถ้าตั้งค่าincludeDirtrue


2

klawและklaw-syncคุ้มค่าที่จะพิจารณาในเรื่องแบบนี้ เหล่านี้เป็นส่วนหนึ่งของโหนด-FS-พิเศษ


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 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 มันถูกประพันธ์โดย Victor Vu

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

ฉันต้องเพิ่มตามสัญญาขัดห้องสมุดในรายการ

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

1

การใช้ bluebird contract.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 (err | 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;
};

สมมติว่าคุณมีเส้นทางที่เรียกว่า '/ ฐานข้อมูล' ในรูทโปรเจ็กต์โหนดของคุณ เมื่อสัญญานี้ได้รับการแก้ไขแล้วก็ควรคายอาร์เรย์ของทุกไฟล์ภายใต้ '/ ฐานข้อมูล'

readdirRecursivePromise('database').then(out => {
    console.log(flatten(out));
}).catch(err => {
    console.log(err);
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.