Nhận tất cả các thư mục trong thư mục nodejs


260

Tôi đã hy vọng điều này sẽ là một điều đơn giản, nhưng tôi không thể tìm thấy bất cứ điều gì ngoài đó để làm như vậy.

Tôi chỉ muốn nhận tất cả các thư mục / thư mục trong một thư mục / thư mục nhất định.

Ví dụ:

<MyFolder>
|- SomeFolder
|- SomeOtherFolder
|- SomeFile.txt
|- SomeOtherFile.txt
|- x-directory

Tôi hy vọng sẽ có được một loạt:

["SomeFolder", "SomeOtherFolder", "x-directory"]

Hoặc ở trên với đường dẫn nếu đó là cách nó được phục vụ ...

Vì vậy, bất cứ điều gì đã tồn tại để làm ở trên?

Câu trả lời:


463

Đây là phiên bản ngắn hơn, đồng bộ của câu trả lời này có thể liệt kê tất cả các thư mục (ẩn hoặc không) trong thư mục hiện tại:

const { lstatSync, readdirSync } = require('fs')
const { join } = require('path')

const isDirectory = source => lstatSync(source).isDirectory()
const getDirectories = source =>
  readdirSync(source).map(name => join(source, name)).filter(isDirectory)

Cập nhật cho nút 10.10.0+

Chúng tôi có thể sử dụng withFileTypestùy chọn mới readdirSyncđể bỏ qua lstatSynccuộc gọi thêm :

const { readdirSync } = require('fs')

const getDirectories = source =>
  readdirSync(source, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name)

14
Cẩn thận, bạn cần đường dẫn tuyệt đối để lấy stat. require('path').resolve(__dirname, file)
Silom

9
@pilau nó chỉ không hoạt động với một đường dẫn tương đối, đó là lý do tại sao bạn cần bình thường hóa nó. Cho rằng bạn có thể sử dụng path.resolve.
Silom

1
@rickysullivan: Bạn cần lặp lại thông qua phản hồi và sử dụng path.resolve (srcpath, Foldername) cho mỗi thư mục bên trong
jarodsmk

5
Vì bạn đang sử dụng es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Lưu ý điều này phá vỡ nếu có liên kết tượng trưng trong thư mục. Sử dụng lstatSyncthay thế.
Dave

103

Nhờ có tính năng cú pháp JavaScript ES6 (ES2015), đây là một lớp lót:

Phiên bản đồng bộ

const { readdirSync, statSync } = require('fs')
const { join } = require('path')

const dirs = p => readdirSync(p).filter(f => statSync(join(p, f)).isDirectory())

Phiên bản không đồng bộ cho Node.js 10+ (thử nghiệm)

const { readdir, stat } = require("fs").promises
const { join } = require("path")

const dirs = async path => {
  let dirs = []
  for (const file of await readdir(path)) {
    if ((await stat(join(path, file))).isDirectory()) {
      dirs = [...dirs, file]
    }
  }
  return dirs
}

30
buộc một cái gì đó vào một dòng làm cho nó ít đọc hơn và do đó ít mong muốn hơn.
rlemon

7
Bạn có thể phù hợp với bất kỳ hướng dẫn trên một dòng, không có nghĩa là bạn nên.
Kevin B

4
Nâng cao. Không chắc chắn tại sao mọi người đánh giá thấp điều này. Một lớp lót là xấu nhưng comon, bạn có thể làm đẹp nó như bạn muốn. Vấn đề là bạn đã học được điều gì đó từ câu trả lời này
Aamir Afridi

27
Nâng cao. Mặc dù đó là lời chỉ trích hợp lệ rằng nó nên được trải rộng trên nhiều dòng. Câu trả lời này cho thấy lợi thế về ngữ nghĩa của cú pháp mới, nhưng tôi nghĩ mọi người đang bị phân tâm bởi cách nó được nhồi nhét vào một dòng. Nếu bạn trải rộng mã này trên cùng một số dòng như câu trả lời được chấp nhận, thì đó vẫn là một biểu thức ngắn gọn hơn của cùng một cơ chế. Tôi nghĩ rằng câu trả lời này có một số giá trị, nhưng có lẽ nó xứng đáng là một chỉnh sửa của câu trả lời được chấp nhận hơn là một câu trả lời khác biệt.
Cứu tinh sắt

23
Các bạn có nghiêm túc không? Đây là một câu trả lời hoàn toàn tốt và hợp lệ! Một dòng, đạo văn - muốn có một cách để hạ thấp lý do tệ hại. Đây không phải là khoa học tên lửa. Đó là một hoạt động rất đơn giản và ngu ngốc! Đừng dài dòng vì lợi ích của nó! Được +1 của tôi chắc chắn!
Mrchief

36

Liệt kê các thư mục bằng cách sử dụng một đường dẫn.

function getDirectories(path) {
  return fs.readdirSync(path).filter(function (file) {
    return fs.statSync(path+'/'+file).isDirectory();
  });
}

1
Điều này khá đơn giản
Paula Fleck

Cuối cùng, tôi có thể hiểu
FourCinnamon0

21

Giải pháp đệ quy

Tôi đến đây để tìm cách lấy tất cả các thư mục con và tất cả các thư mục con của họ, v.v. Dựa trên câu trả lời được chấp nhận , tôi đã viết điều này:

const fs = require('fs');
const path = require('path');

function flatten(lists) {
  return lists.reduce((a, b) => a.concat(b), []);
}

function getDirectories(srcpath) {
  return fs.readdirSync(srcpath)
    .map(file => path.join(srcpath, file))
    .filter(path => fs.statSync(path).isDirectory());
}

function getDirectoriesRecursive(srcpath) {
  return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))];
}

Đây chính xác là những gì tôi đang tìm kiếm và nó dường như hoạt động rất tốt ngoại trừ mọi đường dẫn ngoại trừ đường dẫn đầu tiên xuất hiện như sau: "src \\ page \\ partials \\ nút" thay vì "src / page / partials / button" . Tôi đã thêm bản sửa lỗi bẩn này: var res = getDirectoriesRecursive (srcpath); res = res.map (hàm (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB

1
Một cách ít bẩn hơn để làm điều đó là path.n normalize (). nodejs.org/api/path.html#path_path_n normalize_path
Patrick McElhaney

Bạn đang trả về thư mục cha, điều này không mong muốn. Tôi sẽ cấu trúc lại getDirectoriesRecursive để ngăn chặn điều đó: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Điều này nên làm điều đó:

CoffeeScript (đồng bộ hóa)

fs = require 'fs'

getDirs = (rootDir) ->
    files = fs.readdirSync(rootDir)
    dirs = []

    for file in files
        if file[0] != '.'
            filePath = "#{rootDir}/#{file}"
            stat = fs.statSync(filePath)

            if stat.isDirectory()
                dirs.push(file)

    return dirs

CoffeeScript (không đồng bộ)

fs = require 'fs'

getDirs = (rootDir, cb) ->
    fs.readdir rootDir, (err, files) ->
        dirs = []

        for file, index in files
            if file[0] != '.'
                filePath = "#{rootDir}/#{file}"
                fs.stat filePath, (err, stat) ->
                    if stat.isDirectory()
                        dirs.push(file)
                    if files.length == (index + 1)
                        cb(dirs)

JavaScript (không đồng bộ)

var fs = require('fs');
var getDirs = function(rootDir, cb) { 
    fs.readdir(rootDir, function(err, files) { 
        var dirs = []; 
        for (var index = 0; index < files.length; ++index) { 
            var file = files[index]; 
            if (file[0] !== '.') { 
                var filePath = rootDir + '/' + file; 
                fs.stat(filePath, function(err, stat) {
                    if (stat.isDirectory()) { 
                        dirs.push(this.file); 
                    } 
                    if (files.length === (this.index + 1)) { 
                        return cb(dirs); 
                    } 
                }.bind({index: index, file: file})); 
            }
        }
    });
}

1
Mặc dù nếu điều này là cho một hệ thống sản xuất, bạn thực sự muốn tránh các fsphương pháp đồng bộ .
Aaron Dufour

20
Lưu ý cho bất kỳ người mới nào đọc câu trả lời này: đây là CoffeeScript, không phải JavaScript (bạn tôi đã làm tôi bối rối khi hỏi tại sao JavaScript đột nhiên có khoảng trắng ngữ nghĩa).
DallonF

1
@nicksweet Bạn có thể chuyển đổi nó thành JS không?
mikemaccana

1
Có một số vấn đề rõ ràng với câu trả lời này: nó không có bất kỳ xử lý lỗi nào; (chữ ký gọi lại phải là (err, dirs)); nó sẽ không gọi lại với sự hiện diện của các tập tin hoặc thư mục dấu chấm; nó dễ bị ảnh hưởng bởi tất cả các điều kiện chủng tộc; nó có thể gọi lại trước khi nó kiểm tra tất cả các mục.
1j01

21
Xấu, mọi người cần ngừng phỉ báng api đồng bộ. Việc xác định có nên sử dụng phiên bản đồng bộ hay không không được xác định bởi đó là "sản xuất". Ngoài ra, lặp đi lặp lại quảng cáo rằng các ứng dụng không đồng bộ tốt hơn và đồng bộ hóa là xấu, không có ngữ cảnh, chỉ là không chính xác. Tôi mong muốn cộng đồng JS sẽ ngừng ban hành điều này. Đồng bộ hóa đơn giản hơn (yay) nhưng sẽ chặn vòng lặp tin nhắn (boo). Vì vậy, không sử dụng api đồng bộ hóa trong máy chủ nơi bạn không muốn chặn nhưng hãy thoải mái sử dụng chúng trong tập lệnh xây dựng, ví dụ như điều đó không quan trọng. </ rant>
hcoverlambda

6

Ngoài ra, nếu bạn có thể sử dụng các thư viện bên ngoài, bạn có thể sử dụng filehound. Nó hỗ trợ cuộc gọi lại, lời hứa và cuộc gọi đồng bộ.

Sử dụng lời hứa:

const Filehound = require('filehound');

Filehound.create()
  .path("MyFolder")
  .directory() // only search for directories
  .find()
  .then((subdirectories) => {
    console.log(subdirectories);
  });

Sử dụng gọi lại:

const Filehound = require('filehound');

Filehound.create()
  .path("MyFolder")
  .directory()
  .find((err, subdirectories) => {
    if (err) return console.error(err);

    console.log(subdirectories);
  });

Đồng bộ hóa cuộc gọi:

const Filehound = require('filehound');

const subdirectories = Filehound.create()
  .path("MyFolder")
  .directory()
  .findSync();

console.log(subdirectories);

Để biết thêm thông tin (và ví dụ), hãy xem tài liệu: https://github.com/nspragg/filehound

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


5

Với phiên bản node.js> = v10.13.0, fs.readdirSync sẽ trả về một mảng các đối tượng fs.Dirent nếu withFileTypestùy chọn được đặt thành true.

Vì vậy, bạn có thể sử dụng,

const fs = require('fs')

const directories = source => fs.readdirSync(source, {
   withFileTypes: true
}).reduce((a, c) => {
   c.isDirectory() && a.push(c.name)
   return a
}, [])

điểm tốt, nhưng .filter(c => c.isDirectory())sẽ đơn giản hơn so với sử dụngreduce()
Emmanuel Touzery 30/12/18

Có, nhưng bộ lọc trả về một mảng các đối tượng fs.Dirent là các thư mục. OP muốn tên của các thư mục.
Mayur

1
đúng, tôi vẫn muốn .filter(c => c.isDirectory()).map(c => c.name)qua reducecuộc gọi mặc dù.
Emmanuel Touzery

tôi thấy điểm của bạn. tôi đoán độc giả SO có thể quyết định dựa trên trường hợp sử dụng của họ. Tôi muốn nói rằng việc lặp qua một mảng trong bộ nhớ sẽ không đáng kể về chi phí so với I / O của việc đọc từ đĩa, ngay cả khi bạn đang đọc từ SSD, nhưng như thường lệ nếu ai đó thực sự quan tâm, họ có thể đo.
Emmanuel Touzery 31/12/18

5
 var getDirectories = (rootdir , cb) => {
    fs.readdir(rootdir, (err, files) => {
        if(err) throw err ;
        var dirs = files.map(filename => path.join(rootdir,filename)).filter( pathname => fs.statSync(pathname).isDirectory());
        return cb(dirs);
    })

 }
 getDirectories( myDirectories => console.log(myDirectories));``

4

Sử dụng fs-Extra, hứa hẹn các cuộc gọi fs async và cú pháp async mới đang chờ:

const fs = require("fs-extra");

async function getDirectories(path){
    let filesAndDirectories = await fs.readdir(path);

    let directories = [];
    await Promise.all(
        filesAndDirectories.map(name =>{
            return fs.stat(path + name)
            .then(stat =>{
                if(stat.isDirectory()) directories.push(name)
            })
        })
    );
    return directories;
}

let directories = await getDirectories("/")

3

Và phiên bản async của getDirectories, bạn cần mô-đun async cho việc này:

var fs = require('fs');
var path = require('path');
var async = require('async'); // https://github.com/caolan/async

// Original function
function getDirsSync(srcpath) {
  return fs.readdirSync(srcpath).filter(function(file) {
    return fs.statSync(path.join(srcpath, file)).isDirectory();
  });
}

function getDirs(srcpath, cb) {
  fs.readdir(srcpath, function (err, files) {
    if(err) { 
      console.error(err);
      return cb([]);
    }
    var iterator = function (file, cb)  {
      fs.stat(path.join(srcpath, file), function (err, stats) {
        if(err) { 
          console.error(err);
          return cb(false);
        }
        cb(stats.isDirectory());
      })
    }
    async.filter(files, iterator, cb);
  });
}

2

Câu trả lời này không sử dụng các chức năng chặn như readdirSynchoặc statSync. Nó không sử dụng các phụ thuộc bên ngoài và cũng không tìm thấy chính nó trong chiều sâu của địa ngục gọi lại.

Thay vào đó, chúng tôi sử dụng các tiện ích JavaScript hiện đại như Lời hứa và async-awaitcú pháp. Và kết quả không đồng bộ được xử lý song song; không tuần tự -

const { readdir, stat } =
  require ("fs") .promises

const { join } =
  require ("path")

const dirs = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Promise
        .all
          ( (await readdir (path))
              .map (p => dirs (join (path, p)))
          )
        .then
          ( results =>
              [] .concat (path, ...results)
          )
    : []

Tôi sẽ cài đặt một gói ví dụ và sau đó kiểm tra chức năng của chúng tôi -

$ npm install ramda
$ node

Hãy xem nó hoạt động -

> dirs (".") .then (console.log, console.error)

[ '.'
, 'node_modules'
, 'node_modules/ramda'
, 'node_modules/ramda/dist'
, 'node_modules/ramda/es'
, 'node_modules/ramda/es/internal'
, 'node_modules/ramda/src'
, 'node_modules/ramda/src/internal'
]

Sử dụng một mô-đun tổng quát Parallel, chúng ta có thể đơn giản hóa định nghĩa của dirs-

const Parallel =
  require ("./Parallel")

const dirs = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Parallel (readdir (path))
        .flatMap (f => dirs (join (path, f)))
        .then (results => [ path, ...results ])
    : []

Các Parallelmodule được sử dụng ở trên là một mô hình đã được chiết xuất từ một tập hợp các chức năng được thiết kế để giải quyết một vấn đề tương tự. Để giải thích thêm, hãy xem phần hỏi đáp liên quan này .


1

Phiên bản CoffeeScript của câu trả lời này , với cách xử lý lỗi thích hợp:

fs = require "fs"
{join} = require "path"
async = require "async"

get_subdirs = (root, callback)->
    fs.readdir root, (err, files)->
        return callback err if err
        subdirs = []
        async.each files,
            (file, callback)->
                fs.stat join(root, file), (err, stats)->
                    return callback err if err
                    subdirs.push file if stats.isDirectory()
                    callback null
            (err)->
                return callback err if err
                callback null, subdirs

Phụ thuộc vào async

Ngoài ra, sử dụng một mô-đun cho việc này! (Có các mô-đun cho tất cả mọi thứ. [Cần dẫn nguồn])


1

Nếu bạn cần sử dụng tất cả các asyncphiên bản. Bạn có thể có một cái gì đó như thế này.

  1. Ghi lại độ dài thư mục, sử dụng nó làm chỉ báo để cho biết nếu tất cả các tác vụ thống kê async đã kết thúc.

  2. Nếu các tác vụ thống kê async kết thúc, tất cả các chỉ số tệp đã được kiểm tra, vì vậy hãy gọi lại

Điều này sẽ chỉ hoạt động miễn là Node.js là một luồng đơn, vì nó giả sử không có hai tác vụ không đồng bộ nào sẽ tăng bộ đếm cùng một lúc.

'use strict';

var fs = require("fs");
var path = require("path");
var basePath = "./";

function result_callback(results) {
    results.forEach((obj) => {
        console.log("isFile: " + obj.fileName);
        console.log("fileName: " + obj.isFile);
    });
};

fs.readdir(basePath, (err, files) => {
    var results = [];
    var total = files.length;
    var finished = 0;

    files.forEach((fileName) => {
        // console.log(fileName);
        var fullPath = path.join(basePath, fileName);

        fs.stat(fullPath, (err, stat) => {
            // this will work because Node.js is single thread
            // therefore, the counter will not increment at the same time by two callback
            finished++;

            if (stat.isFile()) {
                results.push({
                    fileName: fileName,
                    isFile: stat.isFile()
                });
            }

            if (finished == total) {
                result_callback(results);
            }
        });
    });
});

Như bạn có thể thấy, đây là một cách tiếp cận "chiều sâu đầu tiên" và điều này có thể dẫn đến địa ngục gọi lại và nó không hoàn toàn "chức năng". Mọi người cố gắng giải quyết vấn đề này bằng Promise, bằng cách gói tác vụ không đồng bộ vào một đối tượng Promise.

'use strict';

var fs = require("fs");
var path = require("path");
var basePath = "./";

function result_callback(results) {
    results.forEach((obj) => {
        console.log("isFile: " + obj.fileName);
        console.log("fileName: " + obj.isFile);
    });
};

fs.readdir(basePath, (err, files) => {
    var results = [];
    var total = files.length;
    var finished = 0;

    var promises = files.map((fileName) => {
        // console.log(fileName);
        var fullPath = path.join(basePath, fileName);

        return new Promise((resolve, reject) => {
            // try to replace fullPath wil "aaa", it will reject
            fs.stat(fullPath, (err, stat) => {
                if (err) {
                    reject(err);
                    return;
                }

                var obj = {
                    fileName: fileName,
                    isFile: stat.isFile()
                };

                resolve(obj);
            });
        });
    });

    Promise.all(promises).then((values) => {
        console.log("All the promise resolved");
        console.log(values);
        console.log("Filter out folder: ");
        values
            .filter((obj) => obj.isFile)
            .forEach((obj) => {
                console.log(obj.fileName);
            });
    }, (reason) => {
        console.log("Not all the promise resolved");
        console.log(reason);
    });
});

Mã tốt! Nhưng tôi nghĩ nó phải là "Lọc ra các tệp:" trong khối Promise.all vì nó đang kiểm tra xem đó có phải là tệp và ghi nhật ký không. :)
Bikal Nepal

1

sử dụng mô đun đường dẫn fs can có thể có thư mục. Điều này sử dụng Promise. Nếu bạn sẽ nhận được điền, bạn có thể thay đổi isDirectory () thành isFile () Nodejs - fs - fs.Stats . Cuối cùng, bạn có thể lấy file'name file'extname và cứ thế trên Nodejs --- Path

var fs = require("fs"),
path = require("path");
//your <MyFolder> path
var p = "MyFolder"
fs.readdir(p, function (err, files) {
    if (err) {
        throw err;
    }
    //this can get all folder and file under  <MyFolder>
    files.map(function (file) {
        //return file or folder path, such as **MyFolder/SomeFile.txt**
        return path.join(p, file);
    }).filter(function (file) {
        //use sync judge method. The file will add next files array if the file is directory, or not. 
        return fs.statSync(file).isDirectory();
    }).forEach(function (files) {
        //The files is array, so each. files is the folder name. can handle the folder.
        console.log("%s", files);
    });
});

Từ hàng đợi đánh giá: Tôi có thể yêu cầu bạn vui lòng thêm một số bối cảnh xung quanh câu trả lời của bạn. Câu trả lời chỉ có mã là khó hiểu. Nó sẽ giúp người hỏi và người đọc tương lai cả nếu bạn có thể thêm thông tin trong bài viết của mình.
help-info.de

1

Phiên bản hoàn toàn không đồng bộ với ES6, chỉ các gói gốc, fs.promises và async / await, thực hiện các thao tác tệp song song:

const fs = require('fs');
const path = require('path');

async function listDirectories(rootPath) {
    const fileNames = await fs.promises.readdir(rootPath);
    const filePaths = fileNames.map(fileName => path.join(rootPath, fileName));
    const filePathsAndIsDirectoryFlagsPromises = filePaths.map(async filePath => ({path: filePath, isDirectory: (await fs.promises.stat(filePath)).isDirectory()}))
    const filePathsAndIsDirectoryFlags = await Promise.all(filePathsAndIsDirectoryFlagsPromises);
    return filePathsAndIsDirectoryFlags.filter(filePathAndIsDirectoryFlag => filePathAndIsDirectoryFlag.isDirectory)
        .map(filePathAndIsDirectoryFlag => filePathAndIsDirectoryFlag.path);
}

Đã thử nghiệm, nó hoạt động độc đáo.


0

Chỉ trong trường hợp bất kỳ ai khác kết thúc ở đây từ một tìm kiếm trên web và Grunt đã có trong danh sách phụ thuộc của họ, câu trả lời cho điều này trở nên tầm thường. Đây là giải pháp của tôi:

/**
 * Return all the subfolders of this path
 * @param {String} parentFolderPath - valid folder path
 * @param {String} glob ['/*'] - optional glob so you can do recursive if you want
 * @returns {String[]} subfolder paths
 */
getSubfolders = (parentFolderPath, glob = '/*') => {
    return grunt.file.expand({filter: 'isDirectory'}, parentFolderPath + glob);
}

0

Một cách tiếp cận đệ quy khác

Cảm ơn Mayur đã biết tôi về withFileTypes. Tôi đã viết mã sau để nhận các tệp của thư mục cụ thể theo cách đệ quy. Nó có thể dễ dàng sửa đổi để chỉ có các thư mục.

const getFiles = (dir, base = '') => readdirSync(dir, {withFileTypes: true}).reduce((files, file) => {
    const filePath = path.join(dir, file.name)
    const relativePath = path.join(base, file.name)
    if(file.isDirectory()) {
        return files.concat(getFiles(filePath, relativePath))
    } else if(file.isFile()) {
        file.__fullPath = filePath
        file.__relateivePath = relativePath
        return files.concat(file)
    }
}, [])

0

lập trình chức năng

const fs = require('fs')
const path = require('path')
const R = require('ramda')

const getDirectories = pathName => {
    const isDirectory = pathName => fs.lstatSync(pathName).isDirectory()
    const mapDirectories = pathName => R.map(name => path.join(pathName, name), fs.readdirSync(pathName))
    const filterDirectories = listPaths => R.filter(isDirectory, listPaths)

    return {
        paths:R.pipe(mapDirectories)(pathName),
        pathsFiltered: R.pipe(mapDirectories, filterDirectories)(pathName)
    }
}
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.