递归获取Node.js目录中的所有文件

76
我对我的函数有一个小问题。我想获取许多目录中的所有文件。目前,我可以检索传递参数的文件中的文件。我想检索以参数形式传递的文件夹中每个文件夹中的html文件。如果我将"test"作为参数输入,则从"test"中检索文件,但我想检索"test / 1 / *. Html","test / 2 / . / .html":
var srcpath2 = path.join('.', 'diapo', result);
function getDirectories(srcpath2) {
                return fs.readdirSync(srcpath2).filter(function (file) {
                    return fs.statSync(path.join(srcpath2, file)).isDirectory();
                });
            }

结果为: [1,2,3]

谢谢!

25个回答

102

看起来glob npm包可以帮助你。以下是如何使用它的示例:

文件层次结构:

test
├── one.html
└── test-nested
    └── two.html

JS代码:

const glob = require("glob");

var getDirectories = function (src, callback) {
  glob(src + '/**/*', callback);
};
getDirectories('test', function (err, res) {
  if (err) {
    console.log('Error', err);
  } else {
    console.log(res);
  }
});

显示:

[ 'test/one.html',
  'test/test-nested',
  'test/test-nested/two.html' ]

5
我找到的最短路线。 - Vlad
我有点失望,因为glob会跳过点文件。如果我们无法通过简单搜索获取点文件,那么这个包的目的是什么? - Asif Ashraf
4
根据文档:您可以在选项中设置dot:true,使glob将点视为普通字符。 -- https://www.npmjs.com/package/glob - Steven
1
@Paul Mougel 这个问题提到要获取所有的“文件”,但你返回的是文件夹。请提供另一种使用 glob 只获取文件列表的解决方案。 - undefined

79

我看到过很多非常冗长的答案,这有点浪费内存空间。一些人还使用像 glob 这样的包,但如果您不想依赖任何包,这是我的解决方案。

const Path = require("path");
const FS   = require("fs");
let Files  = [];

function ThroughDirectory(Directory) {
    FS.readdirSync(Directory).forEach(File => {
        const Absolute = Path.join(Directory, File);
        if (FS.statSync(Absolute).isDirectory()) return ThroughDirectory(Absolute);
        else return Files.push(Absolute);
    });
}

ThroughDirectory("./input/directory/");

这很容易理解。有一个输入目录,程序遍历它。如果其中一个项目也是目录,则继续遍历该目录以此类推。如果它是文件,则将绝对路径添加到数组中。

希望这有所帮助:]


2
const fetchAllFilesFromGivenFolder = (fullPath) => { let files = []; fs.readdirSync(fullPath).forEach(file => { const absolutePath = path.join(fullPath, file); if (fs.statSync(absolutePath).isDirectory()) { const filesFromNestedFolder = fetchAllFilesFromGivenFolder(absolutePath); filesFromNestedFolder.forEach(file => { files.push(file); }) } else return files.push(absolutePath); }); return files } - Alexey Khachatryan
1
Files是一个全局变量,我们可以通过使用一个Files变量或返回顶层结果来改进解决方案。 - undefined

42

使用 ES6 的 yield 关键字

const fs = require('fs');
const path = require('path');

function *walkSync(dir) {
  const files = fs.readdirSync(dir, { withFileTypes: true });
  for (const file of files) {
    if (file.isDirectory()) {
      yield* walkSync(path.join(dir, file.name));
    } else {
      yield path.join(dir, file.name);
    }
  }
}

for (const filePath of walkSync(__dirname)) {
  console.log(filePath);
}

8
从未听说过这个语法和关键字。 - GorvGoyl
@GorvGoyl 这是生成器函数,可以在这里了解更多信息:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators#generator_functions - Lukas Liesis

36

我真的很喜欢Smally的解决方案,但不喜欢它的语法。

相同的解决方案,但稍微容易阅读一些:

const fs = require("fs");
const path = require("path");
let files = [];

const getFilesRecursively = (directory) => {
  const filesInDirectory = fs.readdirSync(directory);
  for (const file of filesInDirectory) {
    const absolute = path.join(directory, file);
    if (fs.statSync(absolute).isDirectory()) {
        getFilesRecursively(absolute);
    } else {
        files.push(absolute);
    }
  }
};

2023年更新
Node v18+(LTS)提供了一个递归标志用于readdir。所以你应该可能使用那个。顺便说一下,v16的EOL是2023年9月
import { readdir } from 'node:fs/promises';

try {
  const files = await readdir('./', { recursive: true });
  console.log(files);
} catch (err) {
  console.error(err);
} 

2
我使用了你的代码片段,非常好用。谢谢你和 Smally ;) - Lasha

16

这是我的答案。像所有好的答案一样,它很难理解:

const isDirectory = path => statSync(path).isDirectory();
const getDirectories = path =>
    readdirSync(path).map(name => join(path, name)).filter(isDirectory);

const isFile = path => statSync(path).isFile();  
const getFiles = path =>
    readdirSync(path).map(name => join(path, name)).filter(isFile);

const getFilesRecursively = (path) => {
    let dirs = getDirectories(path);
    let files = dirs
        .map(dir => getFilesRecursively(dir)) // go through each directory
        .reduce((a,b) => a.concat(b), []);    // map returns a 2d array (array of file arrays) so flatten
    return files.concat(getFiles(path));
};

60
好的答案通常最简单易懂。 - Dara Java
2
这个答案写得很好,不难理解。它有效运行。代码并不多。与glob不同,它是同步的。 - Jared Updike
现在你可以通过一次调用来加载所有的状态,而不是使用 statSync。例如:const dirs = await readdir('./', { withFileTypes: true }) - Avin Kavish

9

使用现代JavaScript(NodeJs 10),您可以使用异步生成器函数,并使用for-await...of循环遍历它们。

// ES modules syntax that is included by default in NodeJS 14.
// For earlier versions, use `--experimental-modules` flag
import fs from "fs/promises"

// or, without ES modules, use this:
// const fs = require('fs').promises

async function run() {
  for await (const file of getFiles()) {
    console.log(file.path)
  }
}

async function* getFiles(path = `./`) {
  const entries = await fs.readdir(path, { withFileTypes: true })

  for (let file of entries) {
    if (file.isDirectory()) {
      yield* getFiles(`${path}${file.name}/`)
    } else {
      yield { ...file, path: path + file.name }
    }
  }
}

run()

为了加快速度,请将第一个循环改为 await getFiles().forEach((file) => ...,将第二个循环改为 for(let i = 0; i < entries.length; i++) - Joel
为什么会更快呢?获取第一个结果所需的时间会更长。同时也需要更多的内存。请通过基准测试来支持这一说法。 - mikabytes

6

打包进库: https://www.npmjs.com/package/node-recursive-directory

https://github.com/vvmspace/node-recursive-directory

文件列表:

const getFiles = require('node-recursive-directory');

(async () => {
    const files = await getFiles('/home');
    console.log(files);
})()

已解析数据的文件列表:

const getFiles = require('node-resursive-directory');
 
(async () => {
    const files = await getFiles('/home', true); // add true
    console.log(files);
})()

您将会得到类似于这样的东西:

  [
      ...,
      {
        fullpath: '/home/vvm/Downloads/images/Some/Some Image.jpg',
        filepath: '/home/vvm/Downloads/images/Some/',
        filename: 'Some Image.jpg',
        dirname: 'Some'
    },
  ]

对我来说,只是运行 require 就会导致 nodemon 崩溃。 - JCraine
@JCraine,这里有个错别字。应该是递归的。 - Siddharth Shyniben

6

被接受的答案需要安装一个包。 如果你想要一个本地的、符合ES6标准的选项:

import { readdirSync } from 'fs'
import { join } from 'path'

function walk(dir) {
  return readdirSync(dir, { withFileTypes: true }).flatMap((file) => file.isDirectory() ? walk(join(dir, file.name)) : join(dir, file.name))
}

这对我很有效。

  • 使用readdirSync读取根目录
  • 然后进行映射,但在执行时要flatten
  • 如果是目录,则进行递归;否则返回文件名

1
伟大的解决方案,值得拥有自己的npm包。 - Normal

3

Node.js v20 版本新增了 recursive 选项到 readdir() 方法中。因此,假设您已经将目录作为第一个参数(** Paul Mougel **):

└── test
    ├── one.html
    └── test-nested
        └── two.html

您可以简洁地完成它,而不需要任何依赖:

const { readdir } = require("node:fs/promises");

async function getFiles(dir) {
  const files = await readdir(dir, { recursive: true });
  const entries = files.map((filename) => `${dir}/${filename}`);
  console.log(entries);
}
getFiles("test);

输出结果如下:

[ 'test/one.html', 'test/test-nested', 'test/test-nested/two.html' ]

这个脚本只显示父文件夹和子文件夹,而不显示每个子文件夹内部的文件。对我来说,@FakeFootball的答案是解决我的问题的正确方法。 - ManuelMB

2

我需要在一个Electron应用程序中做类似的事情:使用TypeScript获取给定基础文件夹中的所有子文件夹,我想到了以下方法:

import { readdirSync, statSync, existsSync } from "fs";
import * as path from "path";

// recursive synchronous "walk" through a folder structure, with the given base path
getAllSubFolders = (baseFolder, folderList = []) => {

    let folders:string[] = readdirSync(baseFolder).filter(file => statSync(path.join(baseFolder, file)).isDirectory());
    folders.forEach(folder => {
        folderList.push(path.join(baseFolder,folder));
        this.getAllSubFolders(path.join(baseFolder,folder), folderList);
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接