大家晚安。我在使用一些简单的递归函数时遇到了麻烦。问题是递归列出给定文件夹及其子文件夹中的所有文件。
目前,我已经成功使用一个简单的函数列出目录中的文件:
fs.readdirSync(copyFrom).forEach((file) => {
let fullPath = path.join(copyFrom, file);
if (fs.lstatSync(fullPath).isDirectory()) {
console.log(fullPath);
} else {
console.log(fullPath);
}
});
我尝试过各种方法,例如
do{} ... while()
,但我无法做到正确。由于我是 javascript 的初学者,我最终决定向你们寻求帮助。
只需添加一个递归调用即可完成:
function traverseDir(dir) {
fs.readdirSync(dir).forEach(file => {
let fullPath = path.join(dir, file);
if (fs.lstatSync(fullPath).isDirectory()) {
console.log(fullPath);
traverseDir(fullPath);
} else {
console.log(fullPath);
}
});
}
以这种方式使用
console.log
可以显示路径,这很棒,但是如果您想对路径做一些更有意义的事情怎么办?例如,也许您想将所有这些收集到一个数组中并将它们传递到其他地方进行处理......
从种子状态开始并在状态变化时扩展值序列的过程称为
unfold
。
const { join } =
require ('path')
const { readdirSync, statSync } =
require ('fs')
const unfold = (f, initState) =>
f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
, () => []
, initState
)
const None =
Symbol ()
const relativePaths = (path = '.') =>
readdirSync (path) .map (p => join (path, p))
const traverseDir = (dir) =>
unfold
( (next, done, [ path = None, ...rest ]) =>
path === None
? done ()
: next ( path
, statSync (path) .isDirectory ()
? relativePaths (path) .concat (rest)
: rest
)
, relativePaths (dir)
)
console.log (traverseDir ('.'))
// [ a, a/1, a/1/1, a/2, a/2/1, a/2/2, b, b/1, ... ]
如果这是你第一次看到这样的节目,
unfold
会感到非常不知所措。下面是 unfold
的简化示例,用于生成小写字母 alphabet
的数组
const unfold = (f, init) =>
f ( (x, next) => [ x, ...unfold (f, next) ]
, () => []
, init
)
const nextLetter = c =>
String.fromCharCode (c.charCodeAt (0) + 1)
const alphabet =
unfold
( (next, done, c) =>
c > 'z'
? done ()
: next ( c // value to add to output
, nextLetter (c) // next state
)
, 'a' // initial state
)
console.log (alphabet)
// [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z ]
如果您仍然遇到困难,我在此处演示的技术在类似问题的答案中进行了更详细的解释
通常,最好使用
fs
模块中的异步功能,因为这可以防止程序在磁盘读取时间较长或网络延迟时挂起。正如其他问答中所展示的那样,展开与异步效果很好
我正在使用以下
getFilesTree
功能。此函数递归列出目录及其子目录中的所有文件,隐藏文件夹和文件除外(以 .
开头)。
import {readdir} from 'node:fs/promises';
import {join, resolve} from 'node:path';
import {parse} from 'node:path';
export async function getFilesTree(dir) {
return await Promise.all(
(await readdir(dir, {withFileTypes: true}))
.filter(child => !child.name.startsWith('.')) // skip hidden
.map(async (child) => {
const base = parse(child.name).base;
const path = resolve(dir, child.name);
return child.isDirectory() ?
{base, path, children: await getFilesTree(join(dir, child.name))} :
{base, path};
}),
);
}
函数本身与 recursive-readdir 库非常相似。结果看起来像这样:
[
{
"base": "file.js",
"path": "/Volumes/Work/file.js"
},
{
"base": "css",
"path": "/Volumes/Work/css",
"children": [
{
"base": "index.css",
"path": "/Volumes/Work/css/index.css"
},
{
"base": "code.css",
"path": "/Volumes/Work/css/code.css"
}
]
}
]
有时候不需要结构化数据,那么你可以使用generator来代替:
import {readdir} from 'node:fs/promises';
import {resolve} from 'node:path';
async function * getFiles(dir) {
for (const dirent of await readdir(dir, {withFileTypes: true})) {
const res = resolve(dir, dirent.name);
if (dirent.isDirectory()) {
yield * getFiles(res);
} else {
yield res;
}
}
}
for await (const file of getFiles('content')) {
console.log(file);
}
借鉴 Jonas Wilms 的答案 - 对于大量文件来说,这确实很慢。这更快(尽管它是异步的)。
const getFilesnamesRecursive = (dir: string, foundFiles: string[]) => {
const files = fs.readdirSync(dir, {withFileTypes: true});
files.forEach((entry: any) =>{
if (entry.isDirectory()){
getFilesnamesRecursive(path.join(dir, entry.name)), foundFiles, logger);
}
else{
foundFiles.push(path.join(dir, entry.name);
}
});
}
另一个速度较慢,因为它使用 readdir 来获取每个文件名然后检查它是否是目录。这种方式更快,因为它使用 withFileTypes: true 选项将每个条目作为 Dirent 对象返回,而不仅仅是文件名。