Ottieni tutte le directory all'interno della directory nodejs


274

Speravo fosse una cosa semplice, ma non riesco a trovare nulla là fuori per farlo.

Voglio solo ottenere tutte le cartelle / directory all'interno di una determinata cartella / directory.

Quindi per esempio:

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

Mi aspetto di ottenere una serie di:

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

O quanto sopra con il percorso se era così che veniva servito ...

Quindi esiste già qualcosa per fare quanto sopra?

Risposte:


492

Ecco una versione più breve e sincrona di questa risposta che può elencare tutte le directory (nascoste o meno) nella directory corrente:

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)

Aggiornamento per il nodo 10.10.0+

Possiamo usare la nuova withFileTypesopzione di readdirSyncper saltare la lstatSyncchiamata extra :

const { readdirSync } = require('fs')

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

16
Attenzione, è necessario il percorso assoluto per ottenere il file stat. require('path').resolve(__dirname, file)
Silom

9
@pilau semplicemente non funziona con un percorso relativo, ecco perché è necessario normalizzarlo. Per questo puoi usare path.resolve.
Silom

1
@rickysullivan: Dovresti iterare la risposta e usare path.resolve (srcpath, foldername) per ogni cartella all'interno
jarodsmk

6
Dato che stai usando es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule

2
Nota che questo si interrompe se ci sono collegamenti simbolici nella directory. Usa lstatSyncinvece.
Dave

107

Grazie alle funzionalità di sintassi di JavaScript ES6 (ES2015) è una riga:

Versione sincrona

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

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

Versione asincrona per Node.js 10+ (sperimentale)

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
}

35
forzare qualcosa su una singola riga lo rende meno leggibile e quindi meno desiderato.
rlemon

7
Puoi adattare qualsiasi istruzione su una riga, non significa che dovresti.
Kevin B

4
Votato. Non sono sicuro del motivo per cui le persone hanno votato in basso Una fodera è cattiva, ma comon, puoi abbellirla come desideri. Il punto è che hai imparato qualcosa da questa risposta
Aamir Afridi

28
Votato. Sebbene sia una critica valida che dovrebbe essere distribuito su più linee. Questa risposta dimostra il vantaggio semantico della nuova sintassi, ma penso che le persone siano distratte da come è stata stipata su una riga. Se distribuisci questo codice sullo stesso numero di righe della risposta accettata, è ancora un'espressione più concisa dello stesso meccanismo. Penso che questa risposta abbia un certo valore, ma forse merita di essere una modifica della risposta accettata piuttosto che una risposta distinta.
Iron Savior

24
Dite sul serio ragazzi? Questa è una risposta perfettamente valida e valida! Una riga, plagio: vorrei che ci fosse un modo per svalutare scuse schifose. Questa non è scienza missilistica. È un'operazione molto semplice e stupida! Non essere prolisso per il gusto di farlo! Ottiene sicuramente il mio +1!
Mrchief

41

Elenca le directory utilizzando un percorso.

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

1
Questo è abbastanza semplice
Paula Fleck il

Finalmente uno che posso capire
FourCinnamon0

25

Soluzione ricorsiva

Sono venuto qui in cerca di un modo per ottenere tutte le sottodirectory e tutte le loro sottodirectory, ecc. Basandomi sulla risposta accettata , ho scritto questo:

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

Questo è esattamente quello che stavo cercando e sembra funzionare alla grande tranne per il fatto che ogni percorso tranne il primo appare così: "src \\ pages \\ partials \\ buttons" invece di questo "src / pages / partials / buttons" . Ho aggiunto questa correzione sporca: var res = getDirectoriesRecursive (srcpath); res = res.map (function (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB

2
Un modo meno sporco per farlo è path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Patrick McElhaney

Stai restituendo la directory principale, che non è desiderabile. Vorrei eseguire il refactoring di getDirectoriesRecursive per evitare che: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav

10

Questo dovrebbe farlo:

CoffeeScript (sincronizzazione)

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

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

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
Anche se questo è per un sistema di produzione, si desidera davvero evitare i fsmetodi sincroni .
Aaron Dufour

20
Nota per tutti i neofiti che leggono questa risposta: questo è CoffeeScript, non JavaScript (il mio amico mi ha mandato un messaggio confuso chiedendomi perché JavaScript avesse improvvisamente uno spazio semantico).
DallonF

1
@nicksweet Puoi convertirlo in JS?
mikemaccana

1
Ci sono alcuni problemi evidenti con questa risposta: non ha alcuna gestione degli errori; (la firma di richiamata dovrebbe essere (err, dirs)); non richiamerà in presenza di file o cartelle punto; è suscettibile a tutte le condizioni di gara; può richiamare prima di aver controllato tutte le voci.
1j01

23
Ug, le persone devono smetterla di diffamare l'API di sincronizzazione. Determinare se una versione di sincronizzazione deve essere utilizzata o meno non è determinato dal fatto che sia "produzione". Anche ripetere fino alla nausea che le API asincrone sono migliori e la sincronizzazione è pessima, senza contesto, non è preciso. Vorrei che la comunità JS smettesse di promulgare questo. La sincronizzazione è più semplice (yay) ma bloccherà il loop dei messaggi (boo). Quindi, non utilizzare le API di sincronizzazione in un server in cui non desideri bloccare, ma sentiti libero di usarle in uno script di build, ad esempio dove non importa. </rant>
hcoverlambda

6

In alternativa, se sei in grado di utilizzare librerie esterne, puoi utilizzare filehound. Supporta callback, promesse e chiamate di sincronizzazione.

Utilizzando le promesse:

const Filehound = require('filehound');

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

Utilizzo dei callback:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Sincronizza chiamata:

const Filehound = require('filehound');

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

console.log(subdirectories);

Per ulteriori informazioni (ed esempi), controlla i documenti: https://github.com/nspragg/filehound

Disclaimer: sono l'autore.


6

Con la versione node.js> = v10.13.0, fs.readdirSync restituirà un array di oggetti fs.Dirent se l' withFileTypesopzione è impostata su true.

Quindi puoi usare,

const fs = require('fs')

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

1
buon punto, ma .filter(c => c.isDirectory())sarebbe più semplice dell'utilizzoreduce()
Emmanuel Touzery

Sì, ma il filtro restituisce un array di oggetti fs.Dirent che sono directory. L'OP voleva nomi di directory.
Mayur

2
vero, preferirei comunque .filter(c => c.isDirectory()).map(c => c.name)la reducechiamata.
Emmanuel Touzery

vedo il tuo punto. Immagino che i lettori possano decidere in base al loro caso d'uso. direi che il looping su un array in memoria dovrebbe essere trascurabile in termini di overhead rispetto all'I / O della lettura da disco, anche se stai leggendo da SSD, ma come al solito se uno ci tiene davvero, possono misurare.
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

Utilizzando fs-extra, che promette le chiamate fs asincrone, e la nuova sintassi di attesa asincrona:

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 una versione asincrona di getDirectories, hai bisogno del modulo asincrono per questo:

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

Questa risposta non utilizza funzioni di blocco come readdirSynco statSync. Non utilizza dipendenze esterne né si trova nelle profondità dell'inferno di callback.

Invece usiamo le moderne comodità JavaScript come Promesse e async-awaitsintassi. E i risultati asincroni vengono elaborati in parallelo; non in sequenza -

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

Installerò un pacchetto di esempio e poi testerò la nostra funzione -

$ npm install ramda
$ node

Vediamolo funzionare -

> 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 modulo generalizzato Parallel, possiamo semplificare la definizione di 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 ])
    : []

Il Parallelmodulo usato sopra era un modello estratto da un insieme di funzioni progettate per risolvere un problema simile. Per ulteriori spiegazioni, vedere queste domande e risposte correlate .


1

Versione CoffeeScript di questa risposta , con una corretta gestione degli errori:

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

Dipende da async

In alternativa, usa un modulo per questo! (Ci sono moduli per tutto. [Citazione necessaria])


1

Se devi usare tutte le asyncversioni. Puoi avere qualcosa di simile.

  1. Registra la lunghezza della directory, la usa come indicatore per sapere se tutte le attività stat asincrone sono terminate.

  2. Se le attività stat async sono terminate, tutte le statistiche del file sono state controllate, quindi chiama il callback

Questo funzionerà solo fintanto che Node.js è un thread singolo, perché presume che non ci siano due attività asincrone che aumenteranno il contatore allo stesso 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);
            }
        });
    });
});

Come puoi vedere, questo è un approccio "prima in profondità", e questo potrebbe provocare un inferno di callback, e non è del tutto "funzionale". Le persone cercano di risolvere questo problema con Promise, avvolgendo l'attività asincrona in un oggetto 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);
    });
});

Buon codice! Ma penso che dovrebbe essere "Filtra file:" nel blocco Promise.all poiché controlla se si tratta di un file e lo registra. :)
Bikal Nepal

1

utilizzare fs 、 path module può ottenere la cartella. questo uso Promise. Se otterrai il riempimento, puoi cambiare isDirectory () in isFile () Nodejs - fs - fs.Stats . Infine , puoi ottenere il file'name file'extname e così via 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);
    });
});

Dalla coda di revisione: posso chiederti di aggiungere un po 'più di contesto alla tua risposta. Le risposte di solo codice sono difficili da capire. Aiuterà sia il richiedente che i futuri lettori se puoi aggiungere ulteriori informazioni nel tuo post.
help-info.de

1

Versione completamente asincrona con ES6, solo pacchetti nativi, fs.promises e async / await, esegue operazioni sui file in parallelo:

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

Testato, funziona bene.


0

Nel caso in cui qualcun altro finisca qui da una ricerca sul web e abbia già Grunt nell'elenco delle dipendenze, la risposta diventa banale. Ecco la mia soluzione:

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

Un altro approccio ricorsivo

Grazie a Mayur per avermi conosciuto withFileTypes. Ho scritto il seguente codice per ottenere ricorsivamente i file di una particolare cartella. Può essere facilmente modificato per ottenere solo directory.

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

programmazione funzionale

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

0

🚀 graph-fs


Installare

npm i graph-fs

Uso

const {Node} = require("graph-fs");
const directory = new Node("/path/to/directory");

const names = directory.children  // <--
    .filter(node => node.is.directory)
    .map(directory => directory.name);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.