How do you get a list of the names of all files present in a directory in Node.js?
我正在尝试使用node.js获取目录中所有文件的名称列表。我想要一个文件名数组的输出。我该怎么做?
您可以使用
雷迪尔
1 2 3 4 5 6 7 8 | const testFolder = './tests/'; const fs = require('fs'); fs.readdir(testFolder, (err, files) => { files.forEach(file => { console.log(file); }); }); |
读写同步
1 2 3 4 5 6 | const testFolder = './tests/'; const fs = require('fs'); fs.readdirSync(testFolder).forEach(file => { console.log(file); }); |
这两种方法的区别在于第一种方法是异步的,因此必须提供一个回调函数,当读取过程结束时将执行该函数。
第二个是同步的,它将返回文件名数组,但它将停止代码的任何进一步执行,直到读取过程结束。
不过,上面的答案不会对目录执行递归搜索。下面是我为递归搜索所做的(使用node walk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); }); |
在我看来,完成这类任务最方便的方法就是使用glob工具。这是node.js的glob包。安装与
1 | npm install glob |
然后使用通配符匹配文件名(示例取自包的网站)
1 2 3 4 5 6 7 8 9 | 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. }) |
获取所有子目录中的文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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')) |
以下是仅使用本机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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); } }); } |
或异步版本(使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 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); } }); }); } |
然后您只需呼叫(同步版本):
1 2 3 | walkSync('path/to/root/dir', function(filePath, stat) { // do something with"filePath"... }); |
或异步版本:
1 2 3 | walk('path/to/root/dir', function(filePath, stat) { // do something with"filePath"... }); |
区别在于执行IO时节点如何阻塞。考虑到上面的API是相同的,您可以使用异步版本来确保最大的性能。
但是,使用同步版本有一个优势。一旦完成了遍历,就可以更容易地执行一些代码,就像在遍历之后的下一个语句中那样。对于异步版本,您需要一些额外的方法来知道何时完成。可能先创建所有路径的映射,然后枚举它们。对于简单的build/util脚本(与高性能Web服务器相比),您可以使用同步版本而不会造成任何损坏。
在ES7中使用承诺与mz/fs异步使用
1 | npm install mz |
然后…
1 2 3 | const fs = require('mz/fs'); fs.readdir('./myDir').then(listing => console.log(listing)) .catch(err => console.error(err)); |
或者,您可以在ES7中的异步函数中编写它们:
1 2 3 4 5 6 | async function myReaddir () { try { const file = await fs.readdir('./myDir/'); } catch (err) { console.error( err ) } }; |
更新递归列表
一些用户已经指定了查看递归列表的愿望(尽管不在问题中)…使用
1 | npm install fs-promise; |
然后…
1 2 3 4 | const fs = require('fs-promise'); fs.walk('./myDir').then( listing => listing.forEach(file => console.log(file.path)) ).catch(err => console.error(err)); |
依赖关系。
1 2 | var fs = require('fs'); var path = require('path'); |
定义。
1 2 3 4 5 6 7 8 | // 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]); }, []); } |
用法。
1 2 3 4 5 6 7 8 9 | 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', ...] |
请注意,
你不会说你想递归地做,所以我假设你只需要目录的直接子目录。
样例代码:
1 2 3 4 5 | const fs = require('fs'); const path = require('path'); fs.readdirSync('your-directory-path') .filter((file) => fs.lstatSync(path.join(folder, file)).isFile()); |
加载
1 | const fs = require('fs'); |
异步读取文件:
1 2 3 | fs.readdir('./dir', function (err, files) { //"files" is an Array with files names }); |
读取文件同步:
1 | var files = fs.readdirSync('./dir'); |
获取
1 2 3 4 5 6 7 8 9 10 11 12 13 | import * as fs from 'fs'; import * as Path from 'path'; function getFilenames(path, extension) { return fs .readdirSync(path) .filter( item => fs.statSync(Path.join(path, item)).isFile() && (extension === undefined || Path.extname(item) === extension) ) .sort(); } |
这是异步递归版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | function ( path, callback){ // the callback gets ( err, files) where files is an array of file names if( typeof callback !== 'function' ) return var result = [] , files = [ path.replace( /\/\s*$/, '' ) ] function traverseFiles (){ if( files.length ) { var name = files.shift() fs.stat(name, function( err, stats){ if( err ){ if( err.errno == 34 ) traverseFiles() // in case there's broken symbolic links or a bad path // skip file instead of sending error else callback(err) } else if ( stats.isDirectory() ) fs.readdir( name, function( err, files2 ){ if( err ) callback(err) else { files = files2 .map( function( file ){ return name + '/' + file } ) .concat( files ) traverseFiles() } }) else{ result.push(name) traverseFiles() } }) } else callback( null, result ) } traverseFiles() } |
采用湖南罗斯托米扬的总体思路,使之更为简洁,增加了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import * as fs from 'fs'; import * as path from 'path'; function fileList(dir, excludeDirs?) { return fs.readdirSync(dir).reduce(function (list, file) { const name = path.join(dir, file); if (fs.statSync(name).isDirectory()) { if (excludeDirs && excludeDirs.length) { excludeDirs = excludeDirs.map(d => path.normalize(d)); const idx = name.indexOf(path.sep); const directory = name.slice(0, idx === -1 ? name.length : idx); if (excludeDirs.indexOf(directory) !== -1) return list; } return list.concat(fileList(name, excludeDirs)); } return list.concat([name]); }, []); } |
示例用法:
1 | console.log(fileList('.', ['node_modules', 'typings', 'bower_components'])); |
如果仍有人搜索,我会这样做:
1 2 3 4 5 6 7 8 9 | import fs from 'fs'; import path from 'path'; const getAllFiles = dir => fs.readdirSync(dir).reduce((files, file) => { const name = path.join(dir, file); const isDirectory = fs.statSync(name).isDirectory(); return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name]; }, []); |
它的工作对我很好
从节点v10.10.0开始,可以将新的
1 2 3 | fs.readdirSync('./dirpath', {withFileTypes: true}) .filter(item => !item.isDirectory()) .map(item => item.name) |
返回的数组的形式为:
1 | ['file1.txt', 'file2.txt', 'file3.txt'] |
fs.dirent类的文档
这是一个类型脚本,可以选择递归,也可以选择错误日志和异步解决方案。可以为要查找的文件名指定正则表达式。
我用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | import * as FsExtra from 'fs-extra' /** * Finds files in the folder that match filePattern, optionally passing back errors . * If folderDepth isn't specified, only the first level is searched. Otherwise anything up * to Infinity is supported. * * @static * @param {string} folder The folder to start in. * @param {string} [filePattern='.*'] A regular expression of the files you want to find. * @param {(Error[] | undefined)} [errors=undefined] * @param {number} [folderDepth=0] * @returns {Promise<string[]>} * @memberof FileHelper */ public static async findFiles( folder: string, filePattern: string = '.*', errors: Error[] | undefined = undefined, folderDepth: number = 0 ): Promise<string[]> { const results: string[] = [] // Get all files from the folder let items = await FsExtra.readdir(folder).catch(error => { if (errors) { errors.push(error) // Save errors if we wish (e.g. folder perms issues) } return results }) // Go through to the required depth and no further folderDepth = folderDepth - 1 // Loop through the results, possibly recurse for (const item of items) { try { const fullPath = Path.join(folder, item) if ( FsExtra.statSync(fullPath).isDirectory() && folderDepth > -1) ) { // Its a folder, recursively get the child folders' files results.push( ...(await FileHelper.findFiles(fullPath, filePattern, errors, folderDepth)) ) } else { // Filter by the file name pattern, if there is one if (filePattern === '.*' || item.search(new RegExp(filePattern, 'i')) > -1) { results.push(fullPath) } } } catch (error) { if (errors) { errors.push(error) // Save errors if we wish } } } return results } |
我制作了一个节点模块来自动执行这个任务:mddir
用法节点mddir"../relative/path/"
安装:npm install mddir-g
为当前目录生成降价:mddir
为任何绝对路径生成:mddir/absolute/path
为相对路径生成:mddir~/documents/whatever。
MD文件将在您的工作目录中生成。
当前忽略节点模块和.git文件夹。
故障排除如果收到错误"node
:no such file or directory",则问题是您的操作系统使用了不同的行尾,如果没有将行尾样式显式设置为unix,mddir就无法分析它们。这通常会影响Windows,但也会影响Linux的某些版本。必须在mddir npm global bin文件夹中执行将行尾设置为unix样式。
获取NPM bin文件夹路径:
CD到那个文件夹
BREW安装dos2unix
dos2unix lib/node_modules/mddir/src/mddir.js
这会将行尾转换为unix而不是dos
然后使用:node mddir"../relative/path/"正常运行。
示例生成的标记文件结构"directorylist.md"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |-- .bowerrc |-- .jshintrc |-- .jshintrc2 |-- Gruntfile.js |-- README.md |-- bower.json |-- karma.conf.js |-- package.json |-- app |-- app.js |-- db.js |-- directoryList.md |-- index.html |-- mddir.js |-- routing.js |-- server.js |-- _api |-- api.groups.js |-- api.posts.js |-- api.users.js |-- api.widgets.js |-- _components |-- directives |-- directives.module.js |-- vendor |-- directive.draganddrop.js |-- helpers |-- helpers.module.js |-- proprietary |-- factory.actionDispatcher.js |-- services |-- services.cardTemplates.js |-- services.cards.js |-- services.groups.js |-- services.posts.js |-- services.users.js |-- services.widgets.js |-- _mocks |-- mocks.groups.js |-- mocks.posts.js |-- mocks.users.js |-- mocks.widgets.js |
使用
1 2 3 4 5 6 7 | const list = require('list-contents'); list("./dist",(o)=>{ if(o.error) throw o.error; console.log('Folders: ', o.dirs); console.log('Files: ', o.files); }); |
只是一个提示:如果您计划对目录中的每个文件执行操作,请尝试vinyl fs(由流构建系统gulp使用)。
您还可以递归地执行。
这有一个NPM模块:
NPM DRE
它允许您将目录树表示为字符串或对象。通过文件回调,您也可以达到您的目标。如果需要,还可以指定要考虑的文件扩展名。
代码如下:
1 2 3 4 5 6 7 8 9 10 | const dree = require('dree'); const fileNames = []; const fileCb = function(file) { fileNames.push(file.name); } dree.scan('path-to-directory', { extensions: [ 'html', 'js' ] }, fileCb); console.log(fileNames); // All the html and js files inside the given folder and its subfolders |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function getFilesRecursiveSync(dir, fileList, optionalFilterFunction) { if (!fileList) { grunt.log.error("Variable 'fileList' is undefined or NULL."); return; } var files = fs.readdirSync(dir); for (var i in files) { if (!files.hasOwnProperty(i)) continue; var name = dir + '/' + files[i]; if (fs.statSync(name).isDirectory()) { getFilesRecursiveSync(name, fileList, optionalFilterFunction); } else { if (optionalFilterFunction && optionalFilterFunction(name) !== true) continue; fileList.push(name); } } } |