Получить все каталоги в каталоге 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
Осторожно, вам нужен абсолютный путь, чтобы получить статистику файла. require('path').resolve(__dirname, file)
Силом

9
@pilau просто не работает с относительным путем, поэтому вам нужно его нормализовать. Для этого вы можете использовать path.resolve.
Силом

1
@rickysullivan: вам нужно будет перебрать ответ и использовать path.resolve (srcpath, foldername) для каждой папки внутри
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
введение чего-либо в одну строку делает его менее читаемым и, следовательно, менее желательным.
rlemon

7
Вы можете поместить любую инструкцию в одну строку, это не значит, что вы должны.
Кевин Б.

4
Upvoted. Не уверен, почему люди понизили это. Один лайнер плохой, но обычный, вы можете украсить его по своему желанию. Дело в том, что вы узнали что-то из этого ответа
Аамир Африди

27
Upvoted. Хотя это справедливая критика, что она должна распространяться на несколько строк. Этот ответ демонстрирует семантическое преимущество нового синтаксиса, но я думаю, что люди отвлекаются на то, как он помещен в одну строку. Если вы разместите этот код на том же количестве строк, что и принятый ответ, он все равно будет более кратким выражением того же механизма. Я думаю, что этот ответ имеет некоторую ценность, но, возможно, он заслуживает того, чтобы редактировать принятый ответ, а не как отдельный ответ.
Железный Спас

23
Вы, ребята, серьезно? Это очень хороший и правильный ответ! Одна строка, плагиат - хотелось бы, чтобы был способ отвратить паршивые оправдания. Это не ракетостроение. Это очень простая и тупая операция! Не будь многословен ради этого! Получает мой +1 точно!
Мркиф

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 \\ pages \\ partials \\ buttons» вместо этого «src / pages / partials / buttons» , Я добавил это грязное исправление: var res = getDirectoriesRecursive (srcpath); res = res.map (function (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))]; }
Надав

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
Ug, люди должны прекратить оскорблять API синхронизации. Определение того, следует ли использовать версию синхронизации, не определяется ее производством. Также повторять до тошноты, что асинхронные API лучше, а синхронизация плохая, без контекста, просто неточно. Я хотел бы, чтобы сообщество JS прекратило обнародовать это. Синхронизация проще (ура), но блокирует цикл обработки сообщений (бу). Поэтому не используйте sync 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 version> = 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, и новый синтаксис await 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

И асинхронная версия getDirectories, вам нужен модуль асинхронности для этого:

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, обернув асинхронную задачу в объект 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);
    });
});

Хороший код! Но я думаю, что это должно быть "Filter out files:" в блоке Promise.all, так как он проверяет, является ли это файл, и регистрирует его. :)
Бикал Непал

1

использовать fs 、 путь модуля может получить папку. это использовать обещание. Если вы получите заливку, вы можете изменить isDirectory () на isFile () Nodejs - fs - fs.Stats . Наконец, вы можете получить имя файла имя файла и так далее 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 / await, выполняют файловые операции параллельно:

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

На всякий случай, если кто-то еще попадет сюда из веб-поиска и у него уже есть Grunt в списке зависимостей, ответ на этот вопрос станет тривиальным. Вот мое решение:

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

Еще один рекурсивный подход

Спасибо Mayur за то, что знал меня 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.