问题
I'm trying to get a list of all the folders present in a directory recursively. I then want to get a list of all the folders in each of those folders.
I want to continue to do this recursively. How can I do this?
const folder = 'folder1/';
const fs = require('fs');
fs.readdir(folder, (err, files) => {
files.forEach(file => {
// detect folder and continue to look for sub-folders
});
});
回答1:
Here's an implementation that recursively finds all of the sub directories using Promises:
const fs = require('fs');
const path = require('path');
// Uncomment polyfill if using Node.js < 10.x
// const util = require('util');
// fs.promises = {
// readdir: util.promisify(fs.readdir),
// stat: util.promisify(fs.stat)
// };
async function listDirectories(dir) {
const fileNames = await fs.promises.readdir(dir);
// Create a directory listing
const listings = await Promise.all(
fileNames.map(async (name) => {
const file = path.join(dir, name);
const stat = await fs.promises.stat(file);
return {file, stat};
})
);
// Create a list of all the sub directories using the listings
const subDirs = listings.filter((listing) => listing.stat.isDirectory())
.map((listing) => listing.file);
// Recurse over the sub directories and add their sub directories to the list of directories
const subSubDirs = await Promise.all(subDirs.map(listDirectories));
return subSubDirs.reduce((a, b) => a.concat(b), subDirs);
}
listDirectories('folder1/').then((dirs) => {
console.log(dirs);
}).catch((err) => {
process.exitCode = 1;
console.error(err);
});
Compared to an equivalent callback based solution I think the promise based solution is easier to understand and less error prone:
const fs = require('fs');
const path = require('path');
function listDirectories(dir, callback) {
fs.readdir(dir, (err, fileNames) => {
if (err) return callback(err);
if (!fileNames.length) return callback(null, []);
// We have to keep track of the remaining operations
let remaining = fileNames.length;
const subDirs = [];
fileNames.forEach((name) => {
const file = path.join(dir, name);
fs.stat(file, (err, stats) => {
if (err) return callback(err);
if (stats.isDirectory()) {
subDirs.push(file);
listDirectories(file, (err, subSubDirs) => {
if (err) return callback(err);
subDirs.push(...subSubDirs);
if (!--remaining) {
// We've gathered the sub dirs of this sub dir and this was the last file to check, all done.
callback(null, subDirs);
}
});
} else if (!--remaining) {
// File was not a dir and was the last file to check, all done.
callback(null, subDirs);
}
});
});
});
}
listDirectories('folder1/',(err, dirs) => {
if (err) {
process.exitCode = 1;
console.error(err);
} else {
console.log(dirs);
}
});
回答2:
Level -99: Callback Hell
It's suffocating down here and I need to get some air. We can easily get to the surface by use of fs.promises
and the newer async-await
syntaxes -
// main.js
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)
)
: []
dirs (process.argv[2]) .then (console.log, console.error)
In my terminal, I install a sample package and then test the program on our project's directory -
$ npm install ramda
$ node main.js .
[ '.'
, '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'
]
We could add a depth
parameter which controls how deep dirs
should recur -
// main.js
const { readdir, stat } =
require ('fs') .promises
const { join } =
require ('path')
const dirs = async (path = ".", depth = Infinity) =>
(await stat (path)) .isDirectory ()
? depth === -1
? []
: Promise
.all
( (await readdir (path))
.map (p => dirs (join (path, p), depth - 1))
)
.then
( results =>
[] .concat (path, ...results)
)
: []
dirs (process.argv[2], process.argv[3]) .then (console.log, console.error)
Testing in the terminal again, we show various paths and depths -
$ node main.js . 1
[ '.'
, 'node_modules'
]
$ node main.js . 2
[ '.'
, 'node_modules'
, 'node_modules/ramda'
]
$ node main.js node_modules/ 1
[ 'node_modules/'
, 'node_modules/ramda'
]
$ node main.js node_modules/ 2
[ 'node_modules/'
, 'node_modules/ramda'
, 'node_modules/ramda/dist'
, 'node_modules/ramda/es'
, 'node_modules/ramda/src'
]
Going Higher-Level
This question is related to another question I answered here. The answer there uses a variety of techniques to provide a complete solution but goes on to show certain patterns can be extracted and reused.
In the dirs
solution above we only have one function to review so we cannot identify a pattern. But I'll show how the reusable module that emerged in the other answer, Parallel
, can be applied to this dirs
as well -
// main.js
const { readdir, stat } =
require ('fs') .promises
const { join } =
require ('path')
const Parallel =
require ('./parallel')
const dirs = async (path = ".", depth = Infinity) =>
(await stat (path)) .isDirectory ()
? depth === -1
? []
: Parallel (readdir (path))
.flatMap (f => dirs (join (path, f), depth - 1))
.then (results => [ path, ...results ])
: []
dirs (process.argv[2], process.argv[3]) .then (console.log, console.error)
Follow the link above to get an idea of how Parallel
can be used to accomplish similar tasks
回答3:
You can use IsFile and IsDirectory
files.forEach(file => {
fs.stat(file, function (error, stat) {
if (stat.isFile()) // it's a file
if (stat.isDirectory()) // it's a directory
});
});
来源:https://stackoverflow.com/questions/56084258/how-do-you-recursively-get-a-list-of-all-folder-names-in-a-directory-in-node-js