如何在Node.js中获取目录中所有文件的名称列表?

1592

我正在尝试使用Node.js获取目录中所有文件的名称列表。 我希望输出是一个包含文件名的数组。 我该如何实现?


13
fs.readdir 可以工作,但无法使用类似 ls /tmp/*core* 的文件名通配符。请查看 https://github.com/isaacs/node-glob。Globs 甚至可以搜索子目录中的文件。 - Jess
2
如果你也想获取子目录中的文件名,可以尝试使用NPM的readdir-recursive模块。 - Ethan Davis
1
fs.readdir是一个简单的异步解决方案 - 示例在这里 - drorw
请问在 TypeScript 或 JavaScript 中是否有使用异步迭代器的目录遍历器? - Flavien Volken
2022年 - 阅读文档:https://nodejs.org/api/fs.html#fspromisesreaddirpath-options - Dmitry
显示剩余3条评论
32个回答

2120
你可以使用fs.readdirfs.readdirSync方法。fs已经包含在Node.js核心中,所以不需要安装任何东西。

fs.readdir

const testFolder = './tests/';
const fs = require('fs');

fs.readdir(testFolder, (err, files) => {
  files.forEach(file => {
    console.log(file);
  });
});

fs.readdirSync

const testFolder = './tests/';
const fs = require('fs');

fs.readdirSync(testFolder).forEach(file => {
  console.log(file);
});

两种方法的区别在于,第一种是异步的,所以你需要提供一个回调函数,在读取过程结束时执行。
第二种是同步的,它会返回文件名数组,但会阻止代码的进一步执行,直到读取过程结束。

268
注意:readdir函数还会显示目录名称。要过滤掉这些,请使用fs.stat(path, callback(err, stats))stats.isDirectory() - Rob W
5
我应该补充说明,最好使用readdire,因为在Node中您不希望阻塞IO。 - DragonKnight
6
除非你正在使用gulp来读取一个源文件有序依赖的目录,并将它们编译成一个单一的可执行文件。 - r3wt
3
请参考我的答案,查看更新的 Promise 方法(https://dev59.com/nXE85IYBdhLWcg3wikEu#37532027)。 - Evan Carroll
2
@Sancarn 你想尝试解析 ls 命令的输出吗?等到有人创建了一些带有空格和换行符的文件名再试试看吧... - Radon Rosborough
显示剩余5条评论

275

在我看来,完成这样的任务最方便的方式是使用 glob 工具。这里有一个适用于 node.js 的 glob 包。安装命令如下:

npm install glob

然后使用通配符来匹配文件名(示例取自包的网站

var glob = require("glob")

// options is optional
glob("**/*.js", options, function (er, files) {
  // files is an array of filenames.
  // If the `nonull` option is set, and nothing
  // was found, then files is ["**/*.js"]
  // er is an error object or null.
})

如果您计划使用globby,这里有一个示例可以查找当前文件夹下的任何xml文件。

var globby = require('globby');

const paths = await globby("**/*.xml");  

7
这对我来说是最好的解决方案,因为我想要更容易地指定文件类型,而不是进行字符串比较。谢谢。 - Pogrindis
我也喜欢这个,因为在Node中使用通配符几乎是一种基本技能。如果您只想获取文件名,请在选项对象中传递cwd - jcollum
1
如何在glob()函数外获取结果?例如,我想要在console.log()中输出结果,但不想在glob()函数内部输出。 - Lanti
22
@Lanti: glob.sync(pattern, [options]) 方法可能更易于使用,因为它仅返回文件名数组,而无需使用回调函数。更多信息请参考:https://github.com/isaacs/node-glob 。 - Glenn Lawrence
6
如果像我这样寻找使用 Promises 实现的 glob 实用程序,可以尝试 sindresorhus 开发的 globby:https://github.com/sindresorhus/globby。 - Nacho Coloma
1
我已经更新了答案,并展示了如何使用@NachoColoma的评论。 - Mauricio Gracia Gutierrez

192

上面的答案没有进行目录的递归搜索。这是我使用 node-walk 进行递归搜索的做法:npm install walk

var walk    = require('walk');
var files   = [];

// Walker options
var walker  = walk.walk('./test', { followLinks: false });

walker.on('file', function(root, stat, next) {
    // Add this file to the list of files
    files.push(root + '/' + stat.name);
    next();
});

walker.on('end', function() {
    console.log(files);
});

4
fs.readdirSync 更好,这是专门为此创建的本地替代方案。 - Eraden
41
fs.readdirSync不会遍历子目录,除非你愿意编写自己的程序来实现这一点,但是既然已经有npm模块可以解决这个问题,你就不需要这样做了。 - Ruben Tan
7
这是一个指向walk Github仓库和文档的链接: https://github.com/coolaj86/node-walk - santiagoIT
OP并没有询问哪个API可以进行递归读取。无论如何,被接受的答案提供了一个可以作为进行递归读取的基础。 - Igwe Kalu
1
这是一个很棒的函数。快问一下:有没有快速忽略某些目录的方法?我想忽略以.git开头的目录。 - j_d
显示剩余4条评论

188

从Node v10.10.0开始,可以使用fs.readdirfs.readdirSync的新选项withFileTypes结合dirent.isDirectory()函数来过滤目录中的文件名。代码如下:

fs.readdirSync('./dirpath', {withFileTypes: true})
.filter(item => !item.isDirectory())
.map(item => item.name)
返回的数组形式为:
['file1.txt', 'file2.txt', 'file3.txt']

24
2020年人们搜索的内容是这个 - 应该被"固定"。 - Val Redchenko
9
2022年也是如此! - Kaushik R Bangera
2
很好,这回答了关于“文件名”的问题。 - Drenai

129
获取所有子目录中的文件
const fs=require('fs');

function getFiles (dir, files_){
    files_ = files_ || [];
    var files = fs.readdirSync(dir);
    for (var i in files){
        var name = dir + '/' + files[i];
        if (fs.statSync(name).isDirectory()){
            getFiles(name, files_);
        } else {
            files_.push(name);
        }
    }
    return files_;
}

console.log(getFiles('path/to/dir'))

4
为什么要写成if (typeof files_ === 'undefined') files_=[];呢?你只需要写成var files_ = files_ || [];就可以了,两者的意思是一样的。 - jkutianski
4
你忘记在getFiles的开头添加 var fs = require('fs'); - GFoley83
这是一个递归方法。它不支持非常深的文件夹结构,这将导致堆栈溢出。 - Mathias Lykkegaard Lorenzen
4
如果你的文件系统嵌套了11000个目录,那么你可能还有很多其他要担心的事情。:p - Radvylf Programs
它不一定要是11k。这取决于堆栈上放了多少内容,而这种方法对堆栈有相当大的分配。 - Mathias Lykkegaard Lorenzen

78

这里是一个简单的解决方案,只使用原生 fspath 模块:

// sync version
function walkSync(currentDirPath, callback) {
    var fs = require('fs'),
        path = require('path');
    fs.readdirSync(currentDirPath).forEach(function (name) {
        var filePath = path.join(currentDirPath, name);
        var stat = fs.statSync(filePath);
        if (stat.isFile()) {
            callback(filePath, stat);
        } else if (stat.isDirectory()) {
            walkSync(filePath, callback);
        }
    });
}

或者异步版本(使用fs.readdir):

// async version with basic error handling
function walk(currentDirPath, callback) {
    var fs = require('fs'),
        path = require('path');
    fs.readdir(currentDirPath, function (err, files) {
        if (err) {
            throw new Error(err);
        }
        files.forEach(function (name) {
            var filePath = path.join(currentDirPath, name);
            var stat = fs.statSync(filePath);
            if (stat.isFile()) {
                callback(filePath, stat);
            } else if (stat.isDirectory()) {
                walk(filePath, callback);
            }
        });
    });
}

然后您只需调用(同步版本):

walkSync('path/to/root/dir', function(filePath, stat) {
    // do something with "filePath"...
});

或者异步版本:

walk('path/to/root/dir', function(filePath, stat) {
    // do something with "filePath"...
});

不同之处在于节点在执行IO时的阻塞方式。鉴于上面的API是一样的,你可以使用异步版本来保证最大的性能。

然而,使用同步版本有一个优点。在遍历完成后,更容易立即执行某些代码,例如在遍历后面的下一条语句中。对于异步版本,你需要一些额外的方法来知道何时完成操作。或许先创建所有路径的映射表,然后枚举它们。对于简单的构建/实用脚本(与高性能网络服务器相比),你可以使用同步版本而不会造成任何损害。


1
应该将 walk(filePath, callback); 这一行替换为 walkSync(filePath, callback); - MIDE11
3
但是在异步版本中,您仍在使用会阻塞的fs.statSync。您不应该使用fs.stat吗? - DifferentPseudonym

28

使用ES7中的Promise

使用mz/fs进行异步操作

mz模块提供了核心node库的Promise封装版本。使用它们非常简单。首先安装该库...

npm install mz

然后...

const fs = require('mz/fs');
fs.readdir('./myDir').then(listing => console.log(listing))
  .catch(err => console.error(err));

或者您可以在ES7的异步函数中编写它们:

async function myReaddir () {
  try {
    const file = await fs.readdir('./myDir/');
  }
  catch (err) { console.error( err ) }
};

递归列表更新

一些用户已经表达了希望看到递归列表的愿望(虽然没有在问题中提出)...使用fs-promise。它是mz的一个薄包装器。

npm install fs-promise;

那么...

const fs = require('fs-promise');
fs.walk('./myDir').then(
    listing => listing.forEach(file => console.log(file.path))
).catch(err => console.error(err));

6
fs.walk已从fs-promise中移除,因为它在fs中不受支持。(https://github.com/kevinbeaty/fs-promise/issues/28) - adnan

24

非递归版本

您没有说明需要使用递归,因此我假设您只需要目录的直接子级。

示例代码:

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

fs.readdirSync('your-directory-path')
  .filter((file) => fs.lstatSync(path.join(folder, file)).isFile());

23

依赖关系。

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

定义。

// String -> [String]
function fileList(dir) {
  return fs.readdirSync(dir).reduce(function(list, file) {
    var name = path.join(dir, file);
    var isDir = fs.statSync(name).isDirectory();
    return list.concat(isDir ? fileList(name) : [name]);
  }, []);
}

用法。

var DIR = '/usr/local/bin';

// 1. List all files in DIR
fileList(DIR);
// => ['/usr/local/bin/babel', '/usr/local/bin/bower', ...]

// 2. List all file names in DIR
fileList(DIR).map((file) => file.split(path.sep).slice(-1)[0]);
// => ['babel', 'bower', ...]
请注意,fileList 过于乐观。对于任何重要的事情,请添加一些错误处理。

1
我还添加了一个excludeDirs数组参数。这使得它有所改变,如果您想要的话,也许您应该编辑它。否则,我会在另一个答案中添加它。https://gist.github.com/AlecTaylor/f3f221b4fb86b4375650 - A T
1
不错!你应该发布自己的答案,因为它是一个有用的扩展。让我们保持这个问题没有特色。 - Hunan Rostomyan
如果您的输入是一个目录,其中包含/Users/user/Desktop/project/example/Users/user/Desktop/project/example/constraints.txt,那么这将导致错误。请仅返回翻译后的文本内容。 - undefined

20

我从你的问题中推断出,你只想要文件名而不是目录名。

目录结构示例

animals
├── all.jpg
├── mammals
│   └── cat.jpg
│   └── dog.jpg
└── insects
    └── bee.jpg

Walk函数

感谢Justin Maier这个代码片段中的贡献。

如果您只想要文件路径的数组,请使用return_object: false

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

async function walk(dir) {
    let files = await fs.readdir(dir);
    files = await Promise.all(files.map(async file => {
        const filePath = path.join(dir, file);
        const stats = await fs.stat(filePath);
        if (stats.isDirectory()) return walk(filePath);
        else if(stats.isFile()) return filePath;
    }));

    return files.reduce((all, folderContents) => all.concat(folderContents), []);
}

使用方法

async function main() {
   console.log(await walk('animals'))
}

输出

[
  "/animals/all.jpg",
  "/animals/mammals/cat.jpg",
  "/animals/mammals/dog.jpg",
  "/animals/insects/bee.jpg"
];

2
@justmaier和a.barbieri - 感谢你们的代码和答案! - KyleMit
嗨,如果我想显示文件夹,应该怎么做? 例如: [ "/animals/all.jpg", "/animals/mammals" "/animals/mammals/cat.jpg", "/animals/mammals/dog.jpg", "/animals/insects/bee.jpg" ];有什么解决方案吗? - Aakash
1
嗨@Aakash,尝试在异步函数的最后一个return之前添加files.unshift(dir)。无论如何,最好创建一个新问题,因为它可能有助于其他有相同需求的人并获得更好的反馈。;-) - a.barbieri
嗨@a.barbieri,如果我只想读取前两个级别的文件夹,我该怎么做?例如:我的目录看起来像这样animals/mammals/name,我想在提供一些深度后停止在哺乳动物。 [ "/animals/all.jpg", "/animals/mammals/cat.jpg", "/animals/mammals/dog.jpg", "/animals/insects/bee.jpg" ]; - Aakash
请创建一个新问题并将链接复制/粘贴到评论中。我很乐意回答。 - a.barbieri

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