☰
learnyounode 过滤目录文件列表实战:掌握 fs.readdir 与 path.extname 的异步文件过滤
2026/9/28 2:37:36 网站建设 项目流程
  • 教程
  • CLI

【免费下载链接】learnyounode

Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.

项目地址:https://gitcode.com/gh_mirrors/le/learnyounode
点击查看免费下载

导读

本篇技术指南围绕 learnyounode 项目中的filtered_ls(过滤文件列表)练习展开,讲解如何编写一个接收「目录路径」与「文件扩展名」两个命令行参数、并以异步 I/O 方式输出过滤后文件列表的 Node.js 程序。读完本文你将掌握fs.readdir()回调式异步 API 的正确用法、path.extname()扩展名解析技巧,以及 learnyounode 如何通过包装 fs 模块自动校验「是否使用异步 API」的底层机制,为后续make_it_modular(模块化重构)等练习打下基础。

练习目标:按扩展名过滤目录文件

filtered_ls是 learnyounode 的第三个练习(紧随my_first_io与my_first_async_io之后),其核心任务来自 exercises/filtered_ls/problem.fr.md:编写一个程序,打印出指定目录下的文件列表,并依据文件的扩展名进行过滤。

程序运行时需要接收两个命令行参数:

  • 第一个参数:目录路径,例如/chemin/du/dossier/(即/path/to/dir/);
  • 第二个参数:用于过滤的扩展名,例如txt。

过滤规则十分明确:若第二个参数是txt,则只输出文件名以.txt结尾的文件。这里有一个容易被忽视的细节——第二个参数不会自带开头的.,因此你必须在程序中自行拼接(例如'.' + process.argv[3]),这一点在英文原文problem.md中也有同样的强调。

同时还有两条硬性约束:

  1. 输出格式:过滤后的文件名必须在控制台逐行打印,每个文件占一行;
  2. 必须使用异步 I/O:不允许使用fs.readdirSync之类的同步方法,这是本练习在验证环节会强制检查的关键点。

注意,process.argv数组的前两个元素被 Node 保留用于系统信息(Node 可执行文件路径与脚本路径),所以程序的第一个参数是process.argv[2],第二个参数是process.argv[3]——这是本练习隐含的重要知识点。

核心 API 与提示

fs.readdir():异步读取目录

练习提示给出了本任务的核心 API——fs.readdir()。该方法接收一个路径作为第一个参数、一个回调函数作为第二个参数,回调签名如下:

function callback (err, list) { /* ... */ }

其中list是一个字符串数组,元素为目录下的文件名(不含路径前缀)。err则在读取失败时携带错误对象,成功时为null。

值得指出的是:fs.readdir()并不递归遍历子目录,它只返回给定目录直接包含的文件与子目录名称;而本练习的场景恰好只需要第一层文件,因此用它做过滤十分合适。

fs模块的完整离线文档已随仓库提供,可直接在浏览器中打开阅读:docs-nodejs/fs.html。

path.extname():提取文件扩展名

练习提示还推荐使用 Node 内置的path模块,尤其是extname方法。path.extname('learnyounode.txt')会返回'.txt';对于无扩展名的文件(如Makefile)则返回空字符串''。结合「第二个参数不带点」的约束,标准写法就是:

if (path.extname(file) === '.' + process.argv[3]) { /* 命中过滤条件 */ }

path模块的离线文档同样随仓库提供:docs-nodejs/path.html。

官方参考答案逐行拆解

仓库中提供了官方解法,位于 exercises/filtered_ls/solution/solution.js,全文仅 15 行:

'use strict' const fs = require('fs') const path = require('path') const folder = process.argv[2] const ext = '.' + process.argv[3] fs.readdir(folder, function (err, files) { if (err) return console.error(err) files.forEach(function (file) { if (path.extname(file) === ext) { console.log(file) } }) })

其思路可归纳为四个步骤:

  1. 取参并预处理扩展名:folder取process.argv[2],ext在process.argv[3]前手动拼接.,规避「参数不带点」的坑;
  2. 异步读取目录:调用fs.readdir(folder, callback);
  3. 错误先行:回调内先判err,有错即console.error(err)并提前返回,避免在空files上继续操作;
  4. 逐项过滤并打印:用path.extname(file) === ext判断扩展名是否匹配,命中则console.log(file),天然实现「每行一个文件」。

官方解法选择了forEach+ 条件判断的写法;测试用例 test/filtered_ls/valid_01.js 则展示了另一种等价写法——先用Array.prototype.filter筛出匹配文件,再对结果forEach打印:

const fs = require('fs') const path = require('path') fs.readdir(process.argv[2], function (err, list) { if (err) { console.log(err) } list.filter(function (file) { return path.extname(file) === '.' + process.argv[3] }).forEach(function (file) { console.log(file) }) })

两种风格均可通过验证,读者可按喜好选择。

验证机制:如何强制「必须使用异步 I/O」

本练习最有趣的部分在于它如何自动判定选手是否遵守了「必须异步」的约束。这一逻辑隐藏在 exercises/filtered_ls/exercise.js 中,其实现分为两层:

第一层:phantom wrapper 包装 fs 调用

exercise.js 第 19 行引入了../my_first_io/wrap作为包装模块(见 exercises/my_first_io/wrap.js)。该模块会把子进程中的fs、fs.promises(fsp)与util的所有方法逐一替换为包装函数,通过调用栈($captureStack)判断:如果该 fs 方法是在选手主程序中调用的,就将其记入ctx.fsCalls计数器。于是选手究竟用了哪些fs.*方法、用了多少次,都被完整记录在案。

第二层:verify 处理器判定同步/异步

exercise.js 第 53–72 行的addVerifyProcessor遍历exercise.wrapData.fsCalls中记录的方法名:

  • 若方法名以Sync结尾(如fs.readdirSync()),判定usedSync = true并输出失败提示fail.sync;
  • 否则判定usedAsync = true并输出通过提示pass.async;
  • 若既没记录到同步也没记录到异步调用(选手可能根本没使用 fs 模块),则输出fail.unused。

最终的通过条件是usedAsync && !usedSync——只用异步、零同步调用。反例测试 test/filtered_ls/invalid_01.js 正是用fs.readdirSync写的同步版本,用于验证该检查能正确拦截。

这一设计也解释了为什么filtered_ls不能直接复用my_first_async_io的验证方式:本练习要求的不只是「用了异步」,而是「绝不出现同步调用」,因此需要比普通输出比对更细粒度的调用追踪。

练习运行的输入数据

为了确保选手程序在不同环境下都能稳定验证,练习会在运行时动态构造测试目录。从 exercises/filtered_ls/exercise.js 的addSetup阶段可见其流程:

  1. 在系统临时目录(os.tmpdir())下创建以_learnyounode_<进程PID>命名的测试目录;
  2. 依据 exercises/filtered_ls/file-list.json 中的 15 个文件名(如learnyounode.dat、learnyounode.txt、learnyounode.sql、api.html、README.md、CHANGELOG.md、LICENCE.md、data.json、data.dat、words.dat、w00t.dat、w00t.txt、wrrrrongdat、dat等)批量写入占位内容;
  3. 通过submissionArgs.unshift('md')与submissionArgs.unshift(testDir),将测试目录与扩展名md依次注入选手程序的process.argv——也就是说,官方验证时第二个参数是md,期望输出的是测试目录中所有以.md结尾的文件(即README.md与CHANGELOG.md);
  4. 验证结束后调用addCleanup用rimraf清理临时目录,避免污染系统。

这份file-list.json也直接复用于官方解法与测试用例的参数注入,保证「题目、答案、测试」三方使用同一份数据源。

运行与验证方式

完成练习后,可以用 learnyounode 自带的验证命令检查程序是否正确。英文原文 exercises/filtered_ls/problem.md 给出了标准用法:

$ learnyounode verify filtered-ls.js

其中filtered-ls.js是选手保存的程序文件。verify模式会完整走一遍上文所述的「构造测试目录 → 注入参数 → 包装 fs 调用 → 执行选手程序 → 比对输出并判定异步约束」全流程;而run模式则只负责在本地执行选手程序,方便自行观察输出。

除手工验证外,仓库还通过workshopper-adventure-test驱动测试(见 package.json 的test脚本与devDependencies),其中test/filtered_ls/目录下的valid_01.js、invalid_01.js分别作为「异步通过版」与「同步失败版」的回归样例,持续保障验证器的判定逻辑正确。

与后续练习的衔接

filtered_ls过滤逻辑本身并不复杂,但它是通往下一练习make_it_modular的跳板:make_it_modular要求把「读取目录 + 按扩展名过滤」的整套逻辑抽取为独立模块,以「模块导出函数 + 回调接收结果」的方式组织代码,并配有solution_filter.js示例与 15 个模块级测试样例(见 exercises/make_it_modular)。提前吃透fs.readdir的异步回调模式与path.extname的边界行为,将大幅降低后续模块化改造时的认知负担。

小结

要点说明
参数位置目录路径在process.argv[2],扩展名在process.argv[3],前两个元素为 Node 保留
扩展名处理第二个参数不带.,需自行拼接为'.' + arg
目录读取使用异步fs.readdir(path, callback),回调签名为callback(err, list)
扩展名判断使用path.extname(file) === ext,注意无扩展名文件返回空字符串
输出格式每个匹配文件console.log一行
强制约束禁止任何*Sync调用,验证器通过包装 fs 模块逐方法计数判定
验证命令learnyounode verify filtered-ls.js

通过本文的源码级拆解,读者不仅能在本地写出可被learnyounode verify认可的答案,更能理解其验证器「包装 fs 调用、区分同步/异步」的设计思想——这正是 learnyounode 这类自引导工作坊(self-guided workshops)区别于普通在线题库的价值所在。

  • 教程
  • CLI

【免费下载链接】learnyounode

Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.

项目地址:https://gitcode.com/gh_mirrors/le/learnyounode
点击查看免费下载
上一篇:BugTraceAI-Apex-G4-26B-Q4 vs 其他安全AI模型:为什么它在MITRE ATT&CK测试中获得100%通过率
下一篇:Uni-MoE学术论文复现:从公式推导到代码实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询