Uzyskaj wszystkie katalogi w katalogu nodejs

260

Miałem nadzieję, że będzie to prosta rzecz, ale nie mogę znaleźć niczego, co by to zrobiło.

Chcę tylko uzyskać wszystkie foldery / katalogi w danym folderze / katalogu.

Na przykład:

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

Spodziewałbym się uzyskać szereg:

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

Lub powyższe ze ścieżką, jeśli tak to było podawane ...

Czy istnieje już coś, co można zrobić powyżej?

Grofit
źródło

Odpowiedzi:

463

Oto krótsza, synchroniczna wersja tej odpowiedzi, która może zawierać listę wszystkich katalogów (ukrytych lub nie) w bieżącym katalogu:

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)

Aktualizacja dla węzła 10.10.0+

Możemy skorzystać z nowej withFileTypesopcji readdirSyncpominięcia dodatkowego lstatSyncpołączenia:

const { readdirSync } = require('fs')

const getDirectories = source =>
  readdirSync(source, { withFileTypes: true })
    .filter(dirent => dirent.isDirectory())
    .map(dirent => dirent.name)
Nick McCurdy
źródło
14
Ostrożnie, potrzebujesz absolutnej ścieżki, aby uzyskać statystyki pliku. require('path').resolve(__dirname, file)
Silom
9
@pilau po prostu nie działa ze ścieżką względną, dlatego musisz ją znormalizować. Do tego możesz użyć path.resolve.
Silom
1
@rickysullivan: Musisz iterować odpowiedź i użyć path.resolve (srcpath, foldername) dla każdego folderu w środku
jarodsmk
5
Ponieważ używasz es6:const getDirectories = srcPath => fs.readdirSync(srcPath).filter(file => fs.statSync(path.join(srcPath, file)).isDirectory())
pmrotule
2
Zauważ, że to się psuje, jeśli w katalogu są dowiązania symboliczne. Użyj lstatSynczamiast tego.
Dave
103

Dzięki funkcjom składni JavaScript ES6 (ES2015) jest to jedna linijka:

Wersja synchroniczna

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

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

Wersja asynchroniczna dla Node.js 10+ (eksperymentalna)

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
}
pravdomil
źródło
30
narzucenie czegoś do jednej linii powoduje, że jest mniej czytelny, a zatem mniej pożądany.
rlemon,
7
Możesz zmieścić dowolne instrukcje w jednym wierszu, nie znaczy to, że powinieneś.
Kevin B,
4
Pozytywne. Nie jestem pewien, dlaczego ludzie głosowali za tym. Jedna wkładka jest zła, ale powszechna, możesz ją upiększyć według własnego uznania. Chodzi o to, że nauczyłeś się czegoś z tej odpowiedzi
Aamir Afridi
27
Pozytywne. Chociaż jest uzasadniona krytyka, że ​​należy ją rozłożyć na więcej linii. Ta odpowiedź pokazuje przewagę semantyczną nowej składni, ale myślę, że ludzie rozpraszają się tym, jak została wciśnięta w jedną linię. Jeśli rozłożysz ten kod na tę samą liczbę wierszy co zaakceptowana odpowiedź, nadal będzie to bardziej zwięzłe wyrażenie tego samego mechanizmu. Myślę, że ta odpowiedź ma pewną wartość, ale być może zasługuje na edycję zaakceptowanej odpowiedzi, a nie wyraźną odpowiedź.
Żelazny Zbawiciel
23
Jesteście poważni? To jest w pełni poprawna i ważna odpowiedź! Jedna linijka, plagiatująca - żałuję, że nie ma sposobu na odrzucenie kiepskich wymówek. To nie jest nauka o rakietach. To bardzo prosta i głupia operacja! Nie bądź gadatliwy ze względu na to! Dostaje moje +1 na pewno!
Mrchief
36

Wyświetl listę katalogów, używając ścieżki.

function getDirectories(path) {
  return fs.readdirSync(path).filter(function (file) {
    return fs.statSync(path+'/'+file).isDirectory();
  });
}
Titlacauan
źródło
1
Ten jest dość prosty
Paula Fleck
Wreszcie jeden, który mogę zrozumieć
FourCinnamon0
21

Rozwiązanie rekurencyjne

Przybyłem tutaj w poszukiwaniu sposobu na uzyskanie wszystkich podkatalogów i wszystkich ich podkatalogów itp. Opierając się na przyjętej odpowiedzi , napisałem:

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))];
}
Patrick McElhaney
źródło
Właśnie tego szukałem i wydaje się, że działa świetnie, z wyjątkiem tego, że każda ścieżka oprócz pierwszej wygląda następująco: „src \\ pages \\ partss \\ buttons” zamiast tego „src / pages / partss / buttons” . Dodałem tę brudną poprawkę: var res = getDirectoriesRecursive (srcpath); res = res.map (function (x) {return x.replace (/ \\ / g, "/")}); console.log (res);
PaulB
1
Mniej brudnym sposobem na to jest path.normalize (). nodejs.org/api/path.html#path_path_normalize_path
Patrick McElhaney
Zwracasz katalog nadrzędny, co nie jest pożądane. Dokonałbym refaktoryzacji getDirectoriesRecursive, aby zapobiec: if (recursive) return [srcpath, ...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; else return [...flatten(getDirectories(srcpath).map(getDirectoriesRecursive))]; }
Nadav
10

To powinno to zrobić:

CoffeeScript (synchronizacja)

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

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

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})); 
            }
        }
    });
}
nicksweet
źródło
1
Chociaż jeśli dotyczy to systemu produkcyjnego, naprawdę chcesz uniknąć fsmetod synchronicznych .
Aaron Dufour,
20
Uwaga dla każdego początkującego czytającego tę odpowiedź: to jest CoffeeScript, a nie JavaScript (mój przyjaciel napisał do mnie w zamieszaniu, pytając, dlaczego JavaScript nagle ma semantyczne białe znaki).
DallonF
1
@nicksweet Czy możesz to przekonwertować na JS?
mikemaccana
1
Istnieje kilka rażących problemów z tą odpowiedzią: nie ma żadnej obsługi błędów; (powinien być podpis oddzwonienia (err, dirs)); nie oddzwoni w obecności plików lub folderów dot; jest podatny na wszystkie warunki wyścigu; może oddzwonić, zanim sprawdzi wszystkie wpisy.
1j01
21
Ludzie muszą przestać oczerniać interfejs API synchronizacji. Określenie, czy należy użyć wersji synchronizacyjnej, nie jest determinowane przez jej „produkcję”. Również powtarzanie reklamy na muzeum, że interfejsy asynchroniczne są lepsze i synchronizacja jest zła, bez kontekstu, po prostu nie jest dokładne. Chciałbym, żeby społeczność JS przestała to ogłaszać. Synchronizacja jest prostsza (yay), ale zablokuje pętlę komunikatów (boo). Dlatego nie używaj synchronizowanych interfejsów API na serwerze, na którym nie chcesz blokować, ale możesz użyć ich w skrypcie kompilacji, na przykład tam, gdzie to nie ma znaczenia. </rant>
hcoverlambda
6

Alternatywnie, jeśli możesz korzystać z bibliotek zewnętrznych, możesz użyć filehound. Obsługuje połączenia zwrotne, obietnice i synchronizację połączeń.

Korzystanie z obietnic:

const Filehound = require('filehound');

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

Korzystanie z oddzwaniania:

const Filehound = require('filehound');

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

    console.log(subdirectories);
  });

Synchronizuj połączenie:

const Filehound = require('filehound');

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

console.log(subdirectories);

Aby uzyskać dodatkowe informacje (i przykłady), sprawdź dokumenty: https://github.com/nspragg/filehound

Oświadczenie: Jestem autorem.

nickool
źródło
5

W przypadku wersji node.js> = v10.13.0 fs.readdirSync zwróci tablicę obiektów fs.Dirent, jeśli withFileTypesopcja jest ustawiona na true.

Możesz więc użyć

const fs = require('fs')

const directories = source => fs.readdirSync(source, {
   withFileTypes: true
}).reduce((a, c) => {
   c.isDirectory() && a.push(c.name)
   return a
}, [])
Mayur
źródło
dobra uwaga, ale .filter(c => c.isDirectory())byłoby prostsze niż używaniereduce()
Emmanuel Touzery
Tak, ale filtr zwraca tablicę obiektów fs.Dirent, które są katalogami. OP chciał nazwy katalogów.
Mayur
1
prawda, ja wciąż wolą .filter(c => c.isDirectory()).map(c => c.name)nad reducewezwanie chociaż.
Emmanuel Touzery
rozumiem co masz na myśli. Myślę, że czytelnicy SO mogą decydować na podstawie ich przypadku użycia. powiedziałbym, że zapętlanie tablicy macierzy w pamięci powinno być nieznaczne w narzutu w porównaniu do operacji we / wy odczytu z dysku, nawet jeśli czytasz z dysku SSD, ale jak zwykle, jeśli naprawdę zależy, można to zmierzyć.
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));``
Jonathan Bonzali
źródło
4

Używając fs-extra, który obiecuje asynchroniczne wywołania fs, a nowa oczekuje na składnię asynchroniczną:

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("/")
1mike12
źródło
3

I asynchroniczna wersja getDirectories, potrzebujesz modułu asynchronicznego do tego:

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);
  });
}
JumpLink
źródło
2

Ta odpowiedź nie używa funkcji blokowania takich jak readdirSynclub statSync. Nie wykorzystuje zewnętrznych zależności ani nie znajduje się w głębinach piekła zwrotnego.

Zamiast tego korzystamy z nowoczesnych udogodnień JavaScript, takich jak obietnice i async-awaitskładnie. Wyniki asynchroniczne są przetwarzane równolegle; nie po kolei -

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

Zainstaluję przykładowy pakiet, a następnie przetestuję naszą funkcję -

$ npm install ramda
$ node

Zobaczmy, jak to działa -

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

Za pomocą uogólnionego modułu Parallelmożemy uprościć definicję 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 ])
    : []

Zastosowany Parallelpowyżej moduł był wzorem wyodrębnionym z zestawu funkcji zaprojektowanych w celu rozwiązania podobnego problemu. Aby uzyskać więcej wyjaśnień, zobacz powiązane pytania i odpowiedzi .

Dziękuję Ci
źródło
1

Wersja odpowiedzi CoffeeScript z odpowiednią obsługą błędów:

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

Zależy od asynchronizacji

Możesz też użyć do tego modułu! (Istnieją moduły do ​​wszystkiego. [Potrzebne źródło])

1j01
źródło
1

Jeśli potrzebujesz użyć wszystkich asyncwersji. Możesz mieć coś takiego.

  1. Zapisz długość katalogu, używa go jako wskaźnika, aby stwierdzić, czy wszystkie zadania statystyki asynchronicznej zostały zakończone.

  2. Jeśli zadania asynchroniczne są zakończone, wszystkie statystyki pliku zostały sprawdzone, więc oddzwoń

Będzie to działać tylko tak długo, jak Node.js jest jednym wątkiem, ponieważ zakłada, że ​​żadne dwa zadania asynchroniczne nie zwiększą licznika w tym samym czasie.

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

Jak widać, jest to podejście „głębokość pierwsza”, co może skutkować piekłem zwrotnym i nie jest do końca „funkcjonalne”. Ludzie próbują rozwiązać ten problem za pomocą Promise, zawijając zadanie asynchroniczne w obiekcie 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);
    });
});
code4j
źródło
Dobry kod! Ale myślę, że powinien to być blok „Filtruj pliki:” w bloku Promise.all, ponieważ sprawdza, czy jest to plik i loguje go. :)
Bikal Nepal
1

użyć fs, ścieżka moduł dostał folder. ta obietnica użytkowania. Jeśli dostaniesz wypełnienie, możesz zmienić isDirectory () na isFile () Nodejs - fs - fs.Stats . Na koniec możesz uzyskać nazwę pliku nazwa pliku i tak dalej Nodejs --- Ścieżka

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);
    });
});
Howard
źródło
Z kolejki recenzji: Czy mogę prosić o dodanie dodatkowego kontekstu wokół odpowiedzi. Odpowiedzi zawierające tylko kod są trudne do zrozumienia. Pomoże zarówno pytającemu, jak i przyszłym czytelnikom, czy możesz dodać więcej informacji w swoim poście.
help-info.de
1

Wersja w pełni asynchroniczna z ES6, tylko pakiety natywne, fs.promises i async / czekają, wykonuje operacje na plikach równolegle:

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

Testowane, działa ładnie.

Dávid Veszelovszki
źródło
0

Na wypadek, gdyby ktokolwiek inny znalazł się tutaj podczas wyszukiwania w sieci i gdy Grunt ma już na swojej liście zależności, odpowiedź na to pytanie staje się banalna. Oto moje rozwiązanie:

/**
 * 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);
}
Artif3x
źródło
0

Kolejne podejście rekurencyjne

Dzięki Mayurowi za informację o mnie withFileTypes. Napisałem następujący kod do rekursywnego pobierania plików z określonego folderu. Można go łatwo modyfikować, aby uzyskać tylko katalogi.

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)
    }
}, [])
Szejk Abdul Wahid
źródło
0

programowanie funkcjonalne

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)
    }
}
Roy Alcala
źródło