Obter todos os diretórios no diretório nodejs


260

Eu esperava que isso fosse uma coisa simples, mas não consigo encontrar nada lá fora para fazê-lo.

Eu só quero obter todas as pastas / diretórios dentro de uma determinada pasta / diretório.

Então, por exemplo:

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

Eu esperaria obter uma matriz de:

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

Ou acima com o caminho, se foi assim que foi servido ...

Então, já existe alguma coisa para fazer o acima?

Respostas:


463

Aqui está uma versão mais curta e síncrona desta resposta que pode listar todos os diretórios (ocultos ou não) no diretório atual:

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)

Atualização para o nó 10.10.0+

Podemos usar a nova withFileTypesopção de readdirSyncpara ignorar a lstatSyncchamada extra :

const { readdirSync } = require('fs')

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

14
Cuidado, você precisa do caminho absoluto para obter o status do arquivo. require('path').resolve(__dirname, file)
Silom

9
@ pilau simplesmente não funciona com um caminho relativo, é por isso que você precisa normalizá-lo. Para isso, você pode usar path.resolve.
Silom

1
@rickysullivan: Você precisaria percorrer a resposta eo uso path.resolve (srcpath, foldername) para cada dentro da pasta
jarodsmk

5
Como você está usando es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Observe que isso é interrompido se houver links simbólicos no diretório Use em lstatSyncvez disso.
Dave

103

Graças aos recursos de sintaxe do JavaScript ES6 (ES2015), ele é único:

Versão síncrona

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

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

Versão assíncrona 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
forçar algo a uma única linha torna menos legível e, portanto, menos desejado.
Rlemon #

7
Você pode ajustar qualquer instrução em uma linha, não significa que deveria.
Kevin B

4
Votado. Não sei por que as pessoas votaram contra isso. Um forro é ruim, mas é comum, você pode embelezá-lo como desejar. O ponto é que você aprendeu alguma coisa com esta resposta
Aamir Afridi

27
Votado. Embora seja uma crítica válida que deve se espalhar por mais linhas. Essa resposta demonstra a vantagem semântica da nova sintaxe, mas acho que as pessoas estão se distraindo com a forma como ela foi comprimida em uma linha. Se você espalhar esse código pelo mesmo número de linhas da resposta aceita, ainda será uma expressão mais concisa do mesmo mecanismo. Acho que essa resposta tem algum valor, mas talvez mereça ser uma edição da resposta aceita e não uma resposta distinta.
Iron Saviour

23
Vocês estão falando sério? Esta é uma resposta perfeitamente bem e válida! Uma linha, plagiando - gostaria que houvesse uma maneira de negar desculpas ruins. Isso não é ciência de foguetes. É uma operação muito simples e burra! Não seja detalhado por causa disso! Obtém o meu +1 com certeza!
Mrchief

36

Listar diretórios usando um caminho.

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

1
Este é bem direto
Paula Fleck

Finalmente um que eu possa entender
FourCinnamon0

21

Solução recursiva

Eu vim aqui em busca de uma maneira de obter todos os subdiretórios e todos os subdiretórios, etc. Com base na resposta aceita , escrevi o seguinte:

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

Isso é exatamente o que eu estava procurando e parece funcionar muito bem, exceto que todos os caminhos, exceto o primeiro, aparecem assim: "src \\ páginas \\ parciais \\ botões" em vez de "src / pages / parcials / buttons" . Eu adicionei esta correção suja: var res = getDirectoriesRecursive (srcpath); res = res.map (função (x) {retornar x.replace (/ \\ / g, "/")}); console.log (res);
19417 PaulB

1
Uma maneira menos suja de fazer isso é path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Patrick McElhaney

Você está retornando o diretório pai, o que não é desejável. Eu refatoraria getDirectoriesRecursive para evitar que: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Isso deve servir:

CoffeeScript (sincronização)

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 (assí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 (assí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
Embora isso seja para um sistema de produção, você realmente deseja evitar os fsmétodos síncronos .
Aaron Dufour

20
Observe para qualquer iniciante que leia esta resposta: este é o CoffeeScript, não o JavaScript (meu amigo me enviou uma mensagem de confusão, perguntando por que o JavaScript de repente tinha espaço em branco semântico).
DallonF

1
@nicksweet Você pode converter isso em JS?
Mikemaccana

1
Existem alguns problemas flagrantes com esta resposta: ela não possui nenhum tratamento de erro; (a assinatura de retorno de chamada deve ser (err, dirs)); não retornará a chamada na presença de arquivos ou pastas de ponto; é suscetível a todas as condições de corrida; pode ligar de volta antes de verificar todas as entradas.
1j01

21
Ug, as pessoas precisam parar de difamar a API de sincronização. Determinar se uma versão de sincronização deve ou não ser usada não é determinado por ser "produção". Também repetir ad nauseum que as APIs assíncronas são melhores e a sincronização é ruim, sem contexto, simplesmente não é precisa. Eu gostaria que a comunidade JS parasse de divulgar isso. A sincronização é mais simples (yay), mas bloqueará o loop da mensagem (boo). Portanto, não use APIs de sincronização em um servidor onde você não deseja bloquear, mas fique à vontade para usá-las em um script de construção, por exemplo, onde isso não importa. </rant>
hcoverlambda

6

Como alternativa, se você puder usar bibliotecas externas, poderá usá-lo filehound. Ele suporta retornos de chamada, promessas e chamadas de sincronização.

Usando promessas:

const Filehound = require('filehound');

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

Usando retornos de chamada:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Sincronizar chamada:

const Filehound = require('filehound');

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

console.log(subdirectories);

Para obter mais informações (e exemplos), consulte os documentos: https://github.com/nspragg/filehound

Disclaimer: Eu sou o autor.


5

Com node.js versão> = v10.13.0, fs.readdirSync irá retornar uma matriz de fs.Dirent objetos se withFileTypesopção é definida como true.

Então você pode usar,

const fs = require('fs')

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

bom ponto, mas .filter(c => c.isDirectory())seria mais simples de usarreduce()
Emmanuel Touzery

Sim, mas o filtro retorna uma matriz de objetos fs.Dirent que são diretórios. O OP queria nomes de diretórios.
Mayur

1
verdade, eu ainda preferiria .filter(c => c.isDirectory()).map(c => c.name)a reduceligação.
Emmanuel Touzery

Eu entendo o seu ponto. Eu acho que os leitores podem decidir com base em seus casos de uso. Eu diria que o loop sobre uma matriz na memória deve ser insignificante em sobrecarga em comparação com a E / S da leitura do disco, mesmo se você estiver lendo do SSD, mas, como de costume, se alguém realmente se importa, eles podem 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 as chamadas fs assíncronas, e a nova sintaxe async aguardada:

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

E uma versão assíncrona de getDirectories, você precisa do módulo assíncrono para isso:

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 resposta não usa funções de bloqueio como readdirSyncoustatSync . Ele não usa dependências externas nem se encontra nas profundezas do inferno de retorno de chamada.

Em vez disso, usamos conveniências modernas de JavaScript, como promessas e async-awaitsintaxes. E os resultados assíncronos são processados ​​em paralelo; não sequencialmente -

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

Vou instalar um pacote de exemplo e testarei nossa função -

$ npm install ramda
$ node

Vamos ver isso 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 um módulo generalizado Parallel, podemos simplificar a definição 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 ])
    : []

O Parallelmódulo usado acima era um padrão extraído de um conjunto de funções projetadas para resolver um problema semelhante. Para mais explicações, consulte as perguntas e respostas relacionadas .


1

Versão CoffeeScript desta resposta , com o tratamento adequado de erros:

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 do assíncrono

Como alternativa, use um módulo para isso! (Existem módulos para tudo. [Citação necessário])


1

Se você precisar usar toda a asyncversão. Você pode ter algo parecido com isto.

  1. Registre o comprimento do diretório, use-o como um indicador para saber se todas as tarefas estatísticas assíncronas foram concluídas.

  2. Se as tarefas estatísticas assíncronas forem concluídas, todas as estatísticas do arquivo foram verificadas, portanto, chame o retorno de chamada

Isso funcionará apenas enquanto o Node.js for de thread único, pois assume que não há duas tarefas assíncronas que aumentarão o contador ao mesmo tempo.

'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 você pode ver, essa é uma abordagem "profundidade primeiro" e isso pode resultar em um inferno de retorno de chamada, e não é bem "funcional". As pessoas tentam resolver esse problema com o Promise, envolvendo a tarefa assíncrona em um 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);
    });
});

Bom código! Mas acho que deveria ser "Filtrar arquivos:" no bloco Promise.all, pois está verificando se é um arquivo e registrando-o. :)
Bikal Nepal

1

use fs 、 path module pode ter a pasta este uso Promise. Se você conseguir o preenchimento, poderá alterar isDirectory () para isFile () Nodejs - fs - fs.Stats . Por fim, você pode obter o arquivo'name file'extname e assim por diante 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);
    });
});

Da fila de revisão: posso solicitar que você adicione um pouco mais de contexto à sua resposta. As respostas somente de código são difíceis de entender. Isso ajudará os solicitantes e os futuros leitores, se você puder adicionar mais informações em sua postagem.
help-info.de

1

A versão totalmente assíncrona com o ES6, apenas pacotes nativos, fs.promises e async / waitit, faz operações de arquivo em 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);
}

Testado, funciona bem.


0

Caso alguém mais chegue aqui a partir de uma pesquisa na web e tenha o Grunt já em sua lista de dependências, a resposta será trivial. Aqui está a minha solução:

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

Outra abordagem recursiva

Obrigado a Mayur por me conhecer withFileTypes. Escrevi o seguinte código para obter arquivos de uma pasta específica recursivamente. Pode ser facilmente modificado para obter apenas diretórios.

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

programação 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)
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.