Dapatkan semua direktori dalam nodejs direktori


260

Saya berharap ini akan menjadi hal yang sederhana, tetapi saya tidak dapat menemukan apa pun di luar sana untuk melakukannya.

Saya hanya ingin mendapatkan semua folder / direktori dalam folder / direktori yang diberikan.

Jadi misalnya:

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

Saya berharap mendapatkan array:

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

Atau yang di atas dengan jalan jika itu yang dilayani ...

Jadi apakah ada yang sudah ada untuk melakukan hal di atas?

Jawaban:


463

Berikut ini versi yang lebih pendek dan sinkron dari jawaban ini yang dapat mencantumkan semua direktori (tersembunyi atau tidak) di direktori saat ini:

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)

Pembaruan untuk Node 10.10.0+

Kita dapat menggunakan withFileTypesopsi baru readdirSyncuntuk melewati lstatSyncpanggilan tambahan :

const { readdirSync } = require('fs')

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

14
Hati-hati, Anda perlu jalur absolut untuk mendapatkan file stat. require('path').resolve(__dirname, file)
Silom

9
@ Pilau itu tidak bekerja dengan jalan yang relatif, itu sebabnya Anda perlu menormalkan itu. Untuk itu Anda bisa menggunakan path.resolve.
Silom

1
@rickysullivan: Anda harus beralih melalui respons dan menggunakan path.resolve (srcpath, foldername) untuk setiap folder di dalam
jarodsmk

5
Karena Anda menggunakan es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Perhatikan ini terputus jika ada symlink di direktori. Gunakan lstatSyncsebagai gantinya.
Dave

103

Berkat fitur sintaksis JavaScript ES6 (ES2015), one liner:

Versi sinkron

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

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

Versi asinkron untuk Node.js 10+ (percobaan)

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
memaksa sesuatu ke satu baris membuatnya kurang mudah dibaca dan karenanya kurang diinginkan.
rlemon

7
Anda dapat memasukkan instruksi apa pun pada satu baris, bukan berarti Anda harus melakukannya.
Kevin B

4
Terpilih. Tidak yakin mengapa orang menurunkan ini. Satu liner buruk tetapi umum, Anda dapat mempercantiknya sesuai keinginan. Intinya adalah Anda belajar sesuatu dari jawaban ini
Aamir Afridi

27
Terpilih. Meskipun itu adalah kritik yang valid bahwa itu harus tersebar di lebih banyak baris. Jawaban ini menunjukkan keunggulan semantik dari sintaks baru, tetapi saya pikir orang-orang menjadi terganggu oleh bagaimana itu dijejalkan ke dalam satu baris. Jika Anda menyebarkan kode ini pada jumlah baris yang sama dengan jawaban yang diterima, itu masih merupakan ekspresi yang lebih ringkas dari mekanisme yang sama. Saya pikir jawaban ini memiliki beberapa nilai, tetapi mungkin layak untuk diedit dari jawaban yang diterima daripada jawaban yang berbeda.
Iron Saviour

23
Apakah kalian serius? Ini jawaban yang sangat bagus dan valid! Satu baris, menjiplak - berharap ada cara untuk menurunkan alasan buruk. Ini bukan ilmu roket. Ini operasi yang sangat sederhana dan bodoh! Jangan bertele-tele demi hal itu! Dapatkan +1 saya pasti!
Mrchief

36

Daftar direktori menggunakan jalur.

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

1
Yang ini cukup mudah
Paula Fleck

Akhirnya saya bisa mengerti
FourCinnamon0

21

Solusi rekursif

Saya datang ke sini untuk mencari cara mendapatkan semua subdirektori, dan semua subdirektori mereka, dll. Berdasarkan jawaban yang diterima , saya menulis ini:

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

Ini persis apa yang saya cari dan tampaknya berfungsi dengan baik kecuali bahwa setiap jalur kecuali yang pertama muncul seperti ini: "src \\ pages \\ partials \\ tombol" bukan ini "src / pages / partials / buttons" . Saya menambahkan perbaikan kotor ini: var res = getDirectoriesRecursive (srcpath); res = res.map (fungsi (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB

1
Cara yang kurang kotor untuk melakukannya adalah path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Patrick McElhaney

Anda mengembalikan direktori induk, yang tidak diinginkan. Saya akan refactor getDirectoriesRecursive untuk mencegah hal itu: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Ini harus dilakukan:

CoffeeScript (sinkronisasi)

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

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

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
Meskipun jika ini untuk sistem produksi, Anda benar-benar ingin menghindari fsmetode sinkron .
Aaron Dufour

20
Catatan untuk setiap pemula yang membaca jawaban ini: ini adalah CoffeeScript, bukan JavaScript (teman saya mengirim pesan dengan bingung menanyakan mengapa JavaScript tiba-tiba memiliki ruang semantik).
DallonF

1
@nicksweet Bisakah Anda mengonversikan ini ke JS?
mikemaccana

1
Ada beberapa masalah mencolok dengan jawaban ini: tidak memiliki penanganan kesalahan; (tanda tangan panggilan balik harus (err, dirs)); itu tidak akan memanggil kembali di hadapan file dot atau folder; itu rentan terhadap semua kondisi balapan; mungkin memanggil kembali sebelum memeriksa semua entri.
1j01

21
Ug, orang harus berhenti memfitnah api sinkronisasi. Menentukan apakah versi sinkronisasi harus digunakan atau tidak ditentukan oleh apakah itu menjadi "produksi". Juga mengulangi ad nauseum bahwa api async lebih baik dan sinkronisasi buruk, tanpa konteks, tidak akurat. Saya berharap komunitas JS akan berhenti mengumumkan hal ini. Sinkronisasi lebih sederhana (yay) tetapi akan memblokir loop pesan (boo). Jadi, jangan gunakan api sinkronisasi di server di mana Anda tidak ingin memblokir tetapi merasa bebas untuk menggunakannya dalam skrip build misalnya di mana itu tidak masalah. </rant>
hcoverlambda

6

Atau, jika Anda dapat menggunakan perpustakaan eksternal, Anda dapat menggunakan filehound. Ini mendukung panggilan balik, janji dan sinkronisasi panggilan.

Menggunakan janji:

const Filehound = require('filehound');

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

Menggunakan panggilan balik:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Sinkronkan panggilan:

const Filehound = require('filehound');

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

console.log(subdirectories);

Untuk informasi lebih lanjut (dan contoh), periksa dokumen: https://github.com/nspragg/filehound

Penafian: Saya penulis.


5

Dengan versi node.js> = v10.13.0, fs.readdirSync akan mengembalikan array objek fs.Dirent jika withFileTypesopsi disetel ke true.

Jadi kamu bisa menggunakan,

const fs = require('fs')

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

Poin yang bagus, tetapi .filter(c => c.isDirectory())akan lebih mudah daripada menggunakanreduce()
Emmanuel Touzery

Ya, tetapi filter mengembalikan array dari objek fs.Dirent yang merupakan direktori. OP menginginkan nama-nama direktori.
Mayur

1
benar, saya masih lebih suka .filter(c => c.isDirectory()).map(c => c.name)daripada reducepanggilan.
Emmanuel Touzery

saya mengerti maksud Anda. Saya kira pembaca SO dapat memutuskan berdasarkan kasus penggunaannya. Saya akan mengatakan bahwa pengulangan pada array dalam memori harus diabaikan dalam overhead dibandingkan dengan I / O membaca dari disk, bahkan jika Anda membaca dari SSD, tetapi seperti biasa jika seseorang benar-benar peduli, mereka dapat mengukur.
Emmanuel Touzery

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

Menggunakan fs-extra, yang menjanjikan panggilan async fs, dan sintaks yang baru menunggu async:

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

Dan versi async dari getDirectories, Anda memerlukan modul async untuk ini:

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

Jawaban ini tidak menggunakan fungsi pemblokiran seperti readdirSyncatau statSync. Itu tidak menggunakan dependensi eksternal atau menemukan dirinya di kedalaman neraka panggilan balik.

Alih-alih, kami menggunakan kenyamanan JavaScript modern seperti Janji dan async-awaitsintaksis. Dan hasil asinkron diproses secara paralel; tidak secara berurutan -

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)
          )
    : []

Saya akan menginstal paket contoh, dan kemudian menguji fungsi kami -

$ npm install ramda
$ node

Mari kita lihat itu bekerja -

> 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'
]

Menggunakan modul umum Parallel,, kita dapat menyederhanakan definisi 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 ])
    : []

The Parallelmodul yang digunakan di atas adalah pola yang diekstraksi dari satu set fungsi yang dirancang untuk memecahkan masalah yang sama. Untuk penjelasan lebih lanjut, lihat Tanya Jawab terkait ini .


1

Versi CoffeeScript dari jawaban ini , dengan penanganan kesalahan yang tepat:

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

Tergantung pada async

Atau, gunakan modul untuk ini! (Ada modul untuk semuanya. [Rujukan?])


1

Jika Anda perlu menggunakan semua asyncversi. Anda dapat memiliki sesuatu seperti ini.

  1. Catat panjang direktori, gunakan itu sebagai indikator untuk mengetahui apakah semua tugas stat async selesai.

  2. Jika tugas async stat selesai, semua file stat telah diperiksa, jadi panggil panggilan balik

Ini hanya akan berfungsi selama Node.js adalah utas tunggal, karena mengasumsikan tidak ada dua tugas async yang akan meningkatkan penghitung pada saat yang sama.

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

Seperti yang Anda lihat, ini adalah pendekatan "kedalaman pertama", dan ini bisa mengakibatkan panggilan balik neraka, dan itu tidak cukup "fungsional". Orang-orang mencoba untuk menyelesaikan masalah ini dengan Promise, dengan membungkus tugas async menjadi objek 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);
    });
});

Kode yang baik! Tapi saya pikir itu harus "Saring file:" di blok Promise.all karena memeriksa apakah itu file dan masuk. :)
Bikal Nepal

1

gunakan fs 、 modul path bisa mendapat folder. ini menggunakan Janji. Jika Anda akan mendapatkan isinya , Anda dapat mengubah isDirectory () ke isFile () Nodejs - fs - fs.Stats .Terakhir , Anda bisa mendapatkan file'name file'extname dan seterusnya 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);
    });
});

Dari antrian ulasan: Boleh saya minta Anda untuk menambahkan beberapa konteks di sekitar jawaban Anda. Jawaban khusus kode sulit dipahami. Ini akan membantu penanya dan pembaca di masa mendatang jika Anda dapat menambahkan lebih banyak informasi dalam posting Anda.
help-info.de

1

Versi async sepenuhnya dengan ES6, hanya paket asli, fs.promises dan async / tunggu, apakah operasi file secara paralel:

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

Diuji, itu bekerja dengan baik.


0

Untuk berjaga-jaga jika ada orang lain yang berakhir di sini dari pencarian web, dan Grunt sudah ada dalam daftar ketergantungan mereka, jawaban untuk ini menjadi sepele. Inilah solusi saya:

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

Pendekatan rekursif lain

Terima kasih kepada Mayur karena telah mengenal saya withFileTypes. Saya menulis kode berikut untuk mendapatkan file folder tertentu secara rekursif. Itu dapat dengan mudah dimodifikasi untuk mendapatkan hanya direktori.

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

pemrograman fungsional

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)
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.