【发布时间】:2011-08-15 05:13:49
【问题描述】:
关于使用 fs.readdir 进行异步目录搜索的任何想法?我意识到我们可以引入递归并使用下一个要读取的目录调用读取目录函数,但我有点担心它不是异步的......
有什么想法吗?我看过node-walk,这很棒,但不像 readdir 那样只给我数组中的文件。虽然
寻找类似...的输出
['file1.txt', 'file2.txt', 'dir/file3.txt']
【问题讨论】:
关于使用 fs.readdir 进行异步目录搜索的任何想法?我意识到我们可以引入递归并使用下一个要读取的目录调用读取目录函数,但我有点担心它不是异步的......
有什么想法吗?我看过node-walk,这很棒,但不像 readdir 那样只给我数组中的文件。虽然
寻找类似...的输出
['file1.txt', 'file2.txt', 'dir/file3.txt']
【问题讨论】:
另一个答案,但这次使用 TypeScript:
/**
* Recursively walk a directory asynchronously and obtain all file names (with full path).
*
* @param dir Folder name you want to recursively process
* @param done Callback function, returns all files with full path.
* @param filter Optional filter to specify which files to include,
* e.g. for json files: (f: string) => /.json$/.test(f)
*/
const walk = (
dir: string,
done: (err: Error | null, results ? : string[]) => void,
filter ? : (f: string) => boolean
) => {
let results: string[] = [];
fs.readdir(dir, (err: Error, list: string[]) => {
if (err) {
return done(err);
}
let pending = list.length;
if (!pending) {
return done(null, results);
}
list.forEach((file: string) => {
file = path.resolve(dir, file);
fs.stat(file, (err2, stat) => {
if (stat && stat.isDirectory()) {
walk(file, (err3, res) => {
if (res) {
results = results.concat(res);
}
if (!--pending) {
done(null, results);
}
}, filter);
} else {
if (typeof filter === 'undefined' || (filter && filter(file))) {
results.push(file);
}
if (!--pending) {
done(null, results);
}
}
});
});
});
};
【讨论】:
对于Node 10.3+,这里是for-await 解决方案:
#!/usr/bin/env node
const FS = require('fs');
const Util = require('util');
const readDir = Util.promisify(FS.readdir);
const Path = require('path');
async function* readDirR(path) {
const entries = await readDir(path,{withFileTypes:true});
for(let entry of entries) {
const fullPath = Path.join(path,entry.name);
if(entry.isDirectory()) {
yield* readDirR(fullPath);
} else {
yield fullPath;
}
}
}
async function main() {
const start = process.hrtime.bigint();
for await(const file of readDirR('/mnt/home/media/Unsorted')) {
console.log(file);
}
console.log((process.hrtime.bigint()-start)/1000000n);
}
main().catch(err => {
console.error(err);
});
此解决方案的好处是您可以立即开始处理结果;例如读取我的媒体目录中的所有文件需要 12 秒,但如果我这样做,我可以在几毫秒内得到第一个结果。
【讨论】:
异步
const fs = require('fs')
const path = require('path')
const readdir = (p, done, a = [], i = 0) => fs.readdir(p, (e, d = []) =>
d.map(f => readdir(a[a.push(path.join(p, f)) - 1], () =>
++i == d.length && done(a), a)).length || done(a))
readdir(__dirname, console.log)
同步
const fs = require('fs')
const path = require('path')
const readdirSync = (p, a = []) => {
if (fs.statSync(p).isDirectory())
fs.readdirSync(p).map(f => readdirSync(a[a.push(path.join(p, f)) - 1], a))
return a
}
console.log(readdirSync(__dirname))
异步可读
function readdir (currentPath, done, allFiles = [], i = 0) {
fs.readdir(currentPath, function (e, directoryFiles = []) {
if (!directoryFiles.length)
return done(allFiles)
directoryFiles.map(function (file) {
var joinedPath = path.join(currentPath, file)
allFiles.push(joinedPath)
readdir(joinedPath, function () {
i = i + 1
if (i == directoryFiles.length)
done(allFiles)}
, allFiles)
})
})
}
readdir(__dirname, console.log)
注意:两个版本都将遵循符号链接(与原始 fs.readdir 相同)
【讨论】:
TypeScript 中使用 Array.flat() 处理嵌套返回的基于 Promise 的递归解决方案。
import { resolve } from 'path'
import { Dirent } from 'fs'
import * as fs from 'fs'
function getFiles(root: string): Promise<string[]> {
return fs.promises
.readdir(root, { withFileTypes: true })
.then(dirents => {
const mapToPath = (r: string) => (dirent: Dirent): string => resolve(r, dirent.name)
const directoryPaths = dirents.filter(a => a.isDirectory()).map(mapToPath(root))
const filePaths = dirents.filter(a => a.isFile()).map(mapToPath(root))
return Promise.all<string>([
...directoryPaths.map(a => getFiles(a, include)).flat(),
...filePaths.map(a => Promise.resolve(a))
]).then(a => a.flat())
})
}
【讨论】:
想要同步替代已接受答案的人(我知道我愿意):
var fs = require('fs');
var path = require('path');
var walk = function(dir) {
let results = [], err = null, list;
try {
list = fs.readdirSync(dir)
} catch(e) {
err = e.toString();
}
if (err) return err;
var i = 0;
return (function next() {
var file = list[i++];
if(!file) return results;
file = path.resolve(dir, file);
let stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
let res = walk(file);
results = results.concat(res);
return next();
} else {
results.push(file);
return next();
}
})();
};
console.log(
walk("./")
)
【讨论】:
只是简单的步行
let pending = [baseFolderPath]
function walk () {
pending.shift();
// do stuffs width pending[0] and change pending items
if (pending[0]) walk(pending[0])
}
walk(pending[0])
【讨论】:
qwtel 的 answer 变体,在 TypeScript
中import { resolve } from 'path';
import { readdir } from 'fs/promises';
async function* getFiles(dir: string): AsyncGenerator<string> {
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const res = resolve(dir, entry.name);
if (entry.isDirectory()) {
yield* getFiles(res);
} else {
yield res;
}
}
}
【讨论】:
有一个名为cup-readdir 的新模块可以非常快速地递归搜索目录。在处理深层目录结构时,它使用异步 Promise 并优于许多流行的模块。
它可以返回一个数组中的所有文件并按属性对它们进行排序,但缺少文件过滤和进入符号链接目录等功能。这对于您只想从目录中获取每个文件的大型项目可能很有用。这是他们项目主页的link。
【讨论】:
这里是一个简单的同步递归解决方案
const fs = require('fs')
const getFiles = path => {
const files = []
for (const file of fs.readdirSync(path)) {
const fullPath = path + '/' + file
if(fs.lstatSync(fullPath).isDirectory())
getFiles(fullPath).forEach(x => files.push(file + '/' + x))
else files.push(file)
}
return files
}
用法:
const files = getFiles(process.cwd())
console.log(files)
您可以异步编写它,但没有必要。只需确保输入目录存在且可访问。
【讨论】:
fs.readFileSync(file) 轻松读取文件,所以 x => files.push(fullPath + '/' + x))
fs.readFileSync 可以理解相对路径,因此取决于您从哪里运行它,但可能甚至不需要更改。跨度>
const fs = require('fs/promises');
const getDirRecursive = async (dir) => {
try {
const items = await fs.readdir(dir);
let files = [];
for (const item of items) {
if ((await fs.lstat(`${dir}/${item}`)).isDirectory()) files = [...files, ...(await getDirRecursive(`${dir}/${item}`))];
else files.push({file: item, path: `${dir}/${item}`, parents: dir.split("/")});
}
return files;
} catch (e) {
return e
}
};
用法:await getDirRecursive("./public");
【讨论】:
基于现代承诺的读取目录递归版本:
const fs = require('fs');
const path = require('path');
const readDirRecursive = async (filePath) => {
const dir = await fs.promises.readdir(filePath);
const files = await Promise.all(dir.map(async relativePath => {
const absolutePath = path.join(filePath, relativePath);
const stat = await fs.promises.lstat(absolutePath);
return stat.isDirectory() ? readDirRecursive(absolutePath) : absolutePath;
}));
return files.flat();
}
【讨论】: