Отримайте всі каталоги в каталозі nodejs


260

Я сподівався, що це буде простою справою, але я не можу знайти нічого, щоб це зробити.

Я просто хочу отримати всі папки / каталоги в заданій папці / каталозі.

Так, наприклад:

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

Я б очікував отримати масив:

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

Або вище з контуром, якщо саме так йому подавали ...

То чи існує вже щось, щоб зробити вищезазначене?

Відповіді:


463

Ось коротка, синхронна версія цієї відповіді, яка може перелічити всі каталоги (приховані чи ні) у поточному каталозі:

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)

Оновлення для вузла 10.10.0+

Ми можемо використовувати нове withFileTypes опцію, readdirSyncщоб пропустити додатковий lstatSyncвиклик:

const { readdirSync } = require('fs')

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

14
Обережно, вам потрібен абсолютний шлях, щоб отримати файл stat. require('path').resolve(__dirname, file)
Силом

9
@pilau це просто не працює з відносним шляхом, тому вам потрібно нормалізувати його. Для цього можна використовувати path.resolve.
Силом

1
@rickysullivan: Вам потрібно буде повторити відповідь і використовувати path.resolve (srcpath, ім'я папки) для кожної папки всередині
jarodsmk

5
Оскільки ви використовуєте es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Зверніть увагу на ці перерви, якщо в каталозі є посилання на посилання. Використовуйте lstatSyncзамість цього.
Дейв

103

Завдяки синтаксису JavaScript ES6 (ES2015) це один вкладиш:

Синхронна версія

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

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

Асинхронна версія для Node.js 10+ (експериментальна)

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

7
Ви можете вмістити будь-яку інструкцію в одному рядку, це не означає, що вам слід.
Кевін Б

4
Оголошено. Не впевнений, чому люди протистояли цьому. Один вкладиш поганий, але звичайний, ви можете прикрасити його, як хочете. Справа в тому, що ви дізналися щось із цієї відповіді
Аамір Африді

27
Оголошено. Хоча справедлива критика, що її слід поширювати на більше рядків. Ця відповідь демонструє смислову перевагу нового синтаксису, але я думаю, що люди відволікаються на те, як він був забитий на одну лінію. Якщо ви поширите цей код на ту саму кількість рядків, що і прийнята відповідь, це все-таки більш стисле вираження того ж механізму. Я думаю, що ця відповідь має деяке значення, але, можливо, вона заслуговує на редагування прийнятої відповіді, а не на чітку відповідь.
Залізний Спас

23
Ви серйозно? Це ідеально чудова і достовірна відповідь! Один рядок, плагіат - хотілося б, щоб було способу відмовитись від огидних виправдань. Це не ракетна наука. Це дуже проста і німа операція! Не будьте багатослівними заради цього! Отримує +1 напевно!
Mrchief

36

Перерахуйте каталоги за допомогою шляху.

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

1
Цей досить простий
Паула Флек

Нарешті, я можу зрозуміти
FourCinnamon0

21

Рекурсивне рішення

Я прийшов сюди в пошуках способу отримати всі підкаталоги та всі їхні підкаталоги тощо. Спираючись на прийняту відповідь , я написав це:

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

Це саме те, що я шукав, і, здається, він працює чудово, за винятком того, що кожен шлях, крім першого, виглядає так: "src \\ сторінки \\ часткові \\ кнопки" замість цього "src / сторінки / частки / кнопки" . Я додав це брудне виправлення: var res = getDirectoriesRecursive (srcpath); res = res.map (функція (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB

1
Менш брудний спосіб це зробити path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Патрік МакЕлхані

Ви повертаєте батьківський каталог, що не бажано. Я б рефактор getDirectoriesRecursive, щоб запобігти цьому: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Це слід зробити:

CoffeeScript (синхронізація)

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 (асинхронізація)

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 (асинхронізація)

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
Хоча якщо це стосується виробничої системи, то дуже хочеться уникати синхронних fsметодів.
Аарон Дюфур

20
Зверніть увагу на новачків, які читають цю відповідь: це CoffeeScript, а не JavaScript (мій друг розгублено обміняв мене запитанням, чому у JavaScript раптом з’явився семантичний пробіл).
DallonF

1
@nicksweet Ви можете перетворити це на JS?
mikemaccana

1
У цій відповіді є деякі яскраві проблеми: у ній немає жодних помилок; (підпис зворотного дзвінка повинен бути (err, dirs)); він не передзвонить за наявності точкових файлів чи папок; він сприйнятливий до всіх перегонових умов; він може передзвонити ще до того, як перевірить усі записи.
1j01

21
Наприклад, людям потрібно припинити порушувати api синхронізації. Визначення того, чи повинна бути використана версія синхронізації чи ні, не визначається тим, що це "виробництво". Також повторення рекламного музею про те, що програми асинхронізації кращі, а синхронізація погана, без контексту, просто не є точною. Я б хотів, щоб спільнота JS перестала оприлюднювати це. Синхронізація простіша (так), але блокує цикл повідомлень (boo). Отже, не використовуйте api-файли синхронізації на сервері, де ви не хочете блокувати, але сміливо використовуйте їх у сценарії побудови, наприклад, де це не має значення. </rant>
hcoverlambda

6

Крім того, якщо ви можете використовувати зовнішні бібліотеки, ви можете використовувати filehound. Він підтримує зворотні дзвінки, обіцянки та синхронізацію дзвінків.

Використання обіцянок:

const Filehound = require('filehound');

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

Використання зворотних дзвінків:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Синхронізувати виклик:

const Filehound = require('filehound');

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

console.log(subdirectories);

Для отримання додаткової інформації (та прикладів) ознайомтеся з документами: https://github.com/nspragg/filehound

Відмова: Я автор.


5

З версією node.js> = v10.13.0, fs.readdirSync поверне масив об'єктів fs.Dirent, якщо для withFileTypesпараметра встановлено значенняtrue .

Таким чином, ви можете використовувати,

const fs = require('fs')

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

хороший момент, але .filter(c => c.isDirectory())було б простіше, ніж використовуватиreduce()
Еммануель Тузері

Так, але фільтр повертає масив об’єктів fs.Dirent, які є каталогами. ОП хотів назвати каталоги.
Mayur

1
правда, я б до сих пір вважаю за краще .filter(c => c.isDirectory()).map(c => c.name)над reduceвикликом , хоча.
Еммануель Тузері

я бачу вашу думку. Я думаю, що читачі ТАК можуть вирішити, виходячи з їх використання. Я б сказав, що циклічність на масиві пам'яті має бути незначним у порівнянні з введенням / виведенням читання з диска, навіть якщо ви читаєте з SSD, але, як завжди, якщо це дійсно байдуже, вони можуть виміряти.
Еммануель Тузері

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

Використання fs-extra, який обіцяє виклики асинхронізації fs, і новий очікує синтаксис асинхронізації:

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

А для асинхронної версії getDirectories для цього вам потрібен модуль async :

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

Ця відповідь не використовує функції блокування, як readdirSyncабо statSync. Він не використовує зовнішніх залежностей і не опиняється в глибині пекла зворотного дзвінка.

Натомість ми використовуємо сучасні зручності JavaScript, такі як Обіцянки та async-awaitсинтаксиси. І асинхронні результати обробляються паралельно; не послідовно -

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

Я встановлю прикладний пакет, а потім протестую нашу функцію -

$ npm install ramda
$ node

Давайте подивимося, як це працює -

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

Використовуючи узагальнений модуль, Parallelми можемо спростити визначення 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 ])
    : []

ParallelМодуль , використаний вище був зразком , який був витягнутий з набору функцій , призначених для вирішення подібної проблеми. Щоб отримати додаткові пояснення, див. Відповідні запитання та запитання .


1

Версія цієї відповіді CoffeeScript з правильним поводженням з помилками:

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

Залежить від асинхронізації

Як варіант, використовуйте для цього модуль! (Є модулі для всього. [Потрібне цитування])


1

Якщо вам потрібно використовувати всю asyncверсію. Ви можете мати щось подібне.

  1. Запишіть довжину каталогу і використовуйте його як індикатор, щоб визначити, чи всі завдання асинхронізації статистичні.

  2. Якщо завдання зі статусом асинхронізації закінчені, всі статистичні дані файлів перевірені, тому зателефонуйте до зворотного дзвінка

Це буде працювати лише до тих пір, поки Node.js є однорядним потоком, оскільки передбачає, що жодні дві задачі з асинхронізацією не збільшать лічильник одночасно.

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

Як бачите, це підхід "на першу глибину", і це може призвести до пекельного зворотного дзвінка, і це не зовсім "функціонально". Люди намагаються вирішити цю проблему за допомогою Promise, вкладаючи завдання async в об’єкт 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);
    });
});

Хороший код! Але я думаю, що це повинно бути "Фільтр файлів:" в блоці Promise.all, оскільки він перевіряє, чи це файл, і реєструє його. :)
Bikal Nepal

1

використовувати модуль шляху fs 、 може отримати папку. це використання Обіцяння. Якщо ви отримаєте заливку, ви можете змінити isDirectory () на isFile () Nodejs - fs - fs.Stats. Нарешті, ви можете отримати ім'я файла file'extname і так далі 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);
    });
});

З черги на огляд: чи можу я попросити вас додати ще трохи контексту навколо вашої відповіді. Відповіді, що стосуються лише коду, важко зрозуміти. Це допоможе запитувачу та майбутнім читачам, якщо ви зможете додати більше інформації у своєму дописі.
help-info.de

1

Повністю асинхронна версія з ES6, лише вбудовані пакети, fs.promises та async / wait, виконують файлові операції паралельно:

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

Випробуваний, він працює чудово.


0

На всякий випадок, якщо хтось із них опиниться в Інтернеті, і Грунт вже є у списку залежностей, відповідь на це стає тривіальною. Ось моє рішення:

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

Ще один рекурсивний підхід

Дякую Маюру за те, що мене знав withFileTypes. Я написав наступний код для отримання файлів певної папки рекурсивно. Його можна легко змінити, щоб отримати лише каталоги.

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

функціональне програмування

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)
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.