Obtener todos los directorios dentro del directorio nodejs


260

Esperaba que esto fuera algo simple, pero no puedo encontrar nada para hacerlo.

Solo quiero obtener todas las carpetas / directorios dentro de una carpeta / directorio determinado.

Así por ejemplo:

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

Esperaría obtener una variedad de:

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

O lo anterior con la ruta si así fue como se sirvió ...

Entonces, ¿ya existe algo para hacer lo anterior?

Respuestas:


463

Aquí hay una versión más breve y sincrónica de esta respuesta que puede enumerar todos los directorios (ocultos o no) en el directorio actual:

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)

Actualización para el nodo 10.10.0+

Podemos usar la nueva withFileTypesopción de readdirSyncomitir la lstatSyncllamada adicional :

const { readdirSync } = require('fs')

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

14
Cuidado, necesitas la ruta absoluta para obtener la estadística del archivo. require('path').resolve(__dirname, file)
Silom

99
@pilau simplemente no funciona con una ruta relativa, por eso necesitas normalizarla. Para eso puedes usar path.resolve.
Silom

1
@rickysullivan: Necesitaría iterar a través de la respuesta y usar path.resolve (srcpath, foldername) para cada carpeta dentro
jarodsmk

55
Como está utilizando es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Tenga en cuenta que esto se rompe si hay enlaces simbólicos en el directorio. Usar en su lstatSynclugar.
Dave

103

Gracias a las características de sintaxis de JavaScript ES6 (ES2015) es un revestimiento:

Versión sincrónica

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

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

Versión asincrónica para Node.js 10+ (experimental)

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
forzar algo a una sola línea lo hace menos legible y, por lo tanto, menos deseado.
rlemon

77
Puede ajustar cualquier instrucción en una línea, no significa que deba hacerlo.
Kevin B

44
Votado No estoy seguro de por qué la gente rechazó esto. Un forro es malo pero, puedes embellecerlo como desees. El punto es que aprendiste algo de esta respuesta
Aamir Afridi

27
Votado Aunque es una crítica válida que debería extenderse en más líneas. Esta respuesta demuestra la ventaja semántica de la nueva sintaxis, pero creo que la gente se está distrayendo por cómo se apiñó en una línea. Si distribuye este código en el mismo número de líneas que la respuesta aceptada, sigue siendo una expresión más concisa del mismo mecanismo. Creo que esta respuesta tiene algún valor, pero tal vez merece ser una edición de la respuesta aceptada en lugar de una respuesta distinta.
Iron Savior

23
¿Hablan en serio? ¡Esta es una respuesta perfectamente buena y válida! Una línea, plagio: desearía que hubiera una forma de rechazar las malas excusas. Esto no es ciencia espacial. ¡Es una operación muy simple y tonta! ¡No seas verboso por el bien! Obtiene mi +1 seguro!
Mrchief

36

Listar directorios usando una ruta.

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

1
Este es bastante sencillo
Paula Fleck

Finalmente uno que puedo entender
FourCinnamon0

21

Solución recursiva

Vine aquí en busca de una manera de obtener todos los subdirectorios, y todos sus subdirectorios, etc. Basándome en la respuesta aceptada , escribí esto:

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

Esto es exactamente lo que estaba buscando y parece funcionar muy bien, excepto que cada ruta, excepto la primera, aparece así: "src \\ pages \\ partials \\ buttons" en lugar de este "src / pages / partials / buttons" . Agregué esta corrección sucia: var res = getDirectoriesRecursive (srcpath); res = res.map (function (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB

1
Una forma menos sucia de hacerlo es path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Patrick McElhaney

Está devolviendo el directorio principal, que no es deseable. Refactorizaría getDirectoriesRecursive para evitar eso: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Esto debería hacerlo:

CoffeeScript (sincronización)

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 (asíncrono)

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 (asíncrono)

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
Aunque si esto es para un sistema de producción, realmente desea evitar los fsmétodos sincrónicos .
Aaron Dufour

20
Nota para los novatos que lean esta respuesta: esto es CoffeeScript, no JavaScript (mi amigo me envió un mensaje de confusión preguntándome por qué JavaScript de repente tenía espacios en blanco semánticos).
DallonF

1
@nicksweet ¿Puedes convertir esto a JS?
mikemaccana

1
Hay algunos problemas evidentes con esta respuesta: no tiene ningún manejo de errores; (la firma de devolución de llamada debe ser (err, dirs)); no volverá a llamar en presencia de archivos o carpetas de puntos; es susceptible a todas las condiciones de carrera; puede volver a llamar antes de que haya verificado todas las entradas.
1j01

21
Ug, la gente necesita dejar de vilipendiar la API de sincronización. Determinar si se debe usar o no una versión de sincronización no está determinado por ser "producción". También repitiendo ad nauseum que las API asíncronas son mejores y que la sincronización es mala, sin contexto, simplemente no es precisa. Desearía que la comunidad JS dejara de promulgar esto. La sincronización es más simple (yay) pero bloqueará el bucle de mensajes (boo). Por lo tanto, no use la API de sincronización en un servidor donde no desee bloquear, pero siéntase libre de usarlos en un script de compilación, por ejemplo, donde eso no importa. </rant>
hcoverlambda

6

Alternativamente, si puede usar bibliotecas externas, puede usar filehound. Admite devoluciones de llamada, promesas y llamadas de sincronización.

Usando promesas:

const Filehound = require('filehound');

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

Usando devoluciones de llamada:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Sincronizar llamada:

const Filehound = require('filehound');

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

console.log(subdirectories);

Para obtener más información (y ejemplos), consulte los documentos: https://github.com/nspragg/filehound

Descargo de responsabilidad: soy el autor.


5

Con la versión node.js> = v10.13.0, fs.readdirSync devolverá una matriz de objetos fs.Dirent si la withFileTypesopción está establecida en true.

Entonces puedes usar,

const fs = require('fs')

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

buen punto, pero .filter(c => c.isDirectory())sería más simple que usarreduce()
Emmanuel Touzery

Sí, pero filter devuelve una matriz de fs.Dirent objetos que son directorios. El OP quería nombres de directorios.
Mayur

1
Es cierto, todavía preferiría .filter(c => c.isDirectory()).map(c => c.name)la reducellamada.
Emmanuel Touzery

entiendo tu argumento. Supongo que los lectores SO pueden decidir en función de su caso de uso. Diría que el bucle sobre una matriz en memoria debería ser insignificante en la sobrecarga en comparación con la E / S de lectura desde el disco, incluso si está leyendo desde SSD, pero como de costumbre si realmente le importa, pueden medir.
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

Usando fs-extra, que promete las llamadas asíncronas fs, y la nueva sintaxis asíncrona espera:

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

Y una versión asíncrona de getDirectories, necesita el módulo asíncrono para esto:

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

Esta respuesta no utiliza funciones de bloqueo como readdirSynco statSync. No utiliza dependencias externas ni se encuentra en las profundidades del infierno de devolución de llamadas.

En su lugar, utilizamos conveniencias modernas de JavaScript como Promesas y async-awaitsintaxis. Y los resultados asincrónicos se procesan en paralelo; no secuencialmente

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

Instalaré un paquete de ejemplo y luego probaré nuestra función:

$ npm install ramda
$ node

Vamos a verlo funcionar

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

Usando un módulo generalizado Parallel, podemos simplificar la definición de 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 ])
    : []

El Parallelmódulo utilizado anteriormente era un patrón que se extrajo de un conjunto de funciones diseñadas para resolver un problema similar. Para obtener más explicaciones, consulte estas preguntas y respuestas relacionadas .


1

Versión CoffeeScript de esta respuesta , con el manejo adecuado de errores:

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

Depende de async

¡Alternativamente, use un módulo para esto! (Hay módulos para todo. [Cita requerida])


1

Si necesita usar todas las asyncversiones. Puedes tener algo como esto.

  1. Registre la longitud del directorio, lo utiliza como un indicador para saber si todas las tareas estadísticas asíncronas han finalizado.

  2. Si las tareas de estadísticas asíncronas han finalizado, se ha verificado toda la estadística del archivo, así que llame a la devolución de llamada

Esto solo funcionará mientras Node.js sea un solo subproceso, porque supone que no hay dos tareas asíncronas que aumentarán el contador al mismo tiempo.

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

Como puede ver, este es un enfoque de "profundidad primero", y esto podría resultar en un infierno de devolución de llamada, y no es del todo "funcional". Las personas intentan resolver este problema con Promise envolviendo la tarea asincrónica en un objeto 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);
    });
});

Buen código! Pero creo que debería ser "Filtrar archivos:" en el bloque Promise.all ya que verifica si es un archivo y lo registra. :)
Bikal Nepal

1

use el módulo de ruta fs can puede obtener la carpeta. Este uso Promesa. Si obtendrá el relleno, puede cambiar isDirectory () a isFile () Nodejs - fs - fs.Stats. Por último, puede obtener el archivo 'nombre de archivo' nombre del archivo y así sucesivamente Nodejs --- Ruta

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

De la cola de revisión: ¿ Puedo solicitarle que agregue más contexto alrededor de su respuesta? Las respuestas de solo código son difíciles de entender. Ayudará tanto al autor de la pregunta como a los futuros lectores si puede agregar más información en su publicación.
help-info.de

1

La versión completamente asíncrona con ES6, solo paquetes nativos, fs.promises y async / await, realiza operaciones de archivo en paralelo:

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

Probado, funciona muy bien.


0

En caso de que alguien más termine aquí desde una búsqueda en la web y ya tenga a Grunt en su lista de dependencias, la respuesta a esto se vuelve trivial. Aquí está mi solución:

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

Otro enfoque recursivo

Gracias a Mayur por conocerme withFileTypes. Escribí el siguiente código para obtener archivos de una carpeta particular de forma recursiva. Se puede modificar fácilmente para obtener solo directorios.

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

programación funcional

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)
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.