news 2026/9/28 2:34:40

learnyounode 实战:用 `fs.readdir()` + `path.extname()` 实现按扩展名过滤的目录文件列表(filtered_ls 练习详解)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
learnyounode 实战:用 `fs.readdir()` + `path.extname()` 实现按扩展名过滤的目录文件列表(filtered_ls 练习详解)
  • 教程
  • 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(过滤目录列表)练习为核心,完整讲解如何编写一个读取指定目录、按文件扩展名过滤、并以一行一个文件的方式输出结果的 Node.js 命令行程序。读完本文,你将掌握fs.readdir()异步回调的用法、process.argv的参数解析规则、path.extname()的扩展名提取技巧,并了解该练习背后的自动化验证机制(如何强制要求使用异步 I/O)。所有结论均有本仓库的官方题目、参考答案与测试用例作为依据。

练习目标:从题目要求说起

filtered_ls 是 learnyounode 入门序列中的第三道练习,承接 baby_steps(命令行参数)与 my_first_async_io(异步文件读取)。本练习的完整题目原文见 exercises/filtered_ls/problem.md,核心要求可拆解为四点:

  1. 新建文件:程序必须保存在名为filtered-ls.js的文件中。
  2. 两个命令行参数:第一个参数是目标目录路径(如/path/to/dir/),第二个参数是用于过滤的文件扩展名(如txt)。
  3. 过滤规则:只输出文件名以.扩展名结尾的文件。特别注意——第二个参数传入时不带前导点号(txt而非.txt),程序内部需要自行拼接点号。
  4. 输出与 I/O 要求:每个匹配文件占一行打印到控制台,并且必须使用异步 I/O(禁止fs.readdirSync之类的同步方法)。

题目中还特别提醒了一个新手极易踩坑的点:程序接收的参数不是process.argv数组的开头几个元素,因为 Node 会把这个数组的前两个位置预留给系统信息(第一个是 Node 可执行文件路径,第二个是脚本文件路径)。真正的用户参数从process.argv[2]开始。

从题目信息(problem.md 的 HINTS 小节)可知,这道练习希望用到 Node 内置模块fs(负责读取目录)和path(负责提取扩展名),对应仓库内还内置了官方文档镜像:/docs-nodejs/fs.html 与 docs-nodejs/path.html,可随时查阅。

核心 API 讲解:fs.readdir()与path.extname()

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

fs.readdir()是 fs 模块中用于列出目录内容的异步方法,其第一个参数是目录路径,第二个参数是回调函数,回调签名固定为:

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

其中list是一个由文件名组成的字符串数组,且数组内的名字只包含文件名本身(不含完整路径),因此后续无法直接对它做路径拼接,需要配合path模块处理。如果读取失败,err会携带错误信息;成功时err为null。

fs.readdir()的完整 API 文档(包括fs.readdirSync、fs.promises.readdir等变体)收录在本仓库的 docs-nodejs/fs.html 中,可以作为离线查阅的权威参考资料。

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

path.extname()接收一个文件名字符串,返回其扩展名部分,返回结果自带前导点号。例如:

const path = require('path') path.extname('learnyounode.txt') // => '.txt' path.extname('README.md') // => '.md' path.extname('LICENCE.md') // => '.md' path.extname('learnyounode') // => '' (无扩展名返回空字符串)

这正好与题目“第二个参数不带点号”的规则形成互补:程序中把用户传入的txt拼接成.txt,再与path.extname()的结果做全等比较(===),即可准确过滤。path模块的完整文档同样可在仓库中查阅:docs-nodejs/path.html。

参考答案:一份可直接运行的最小实现

仓库在 exercises/filtered_ls/solution/solution.js 中给出了官方参考实现,代码非常精简,完整如下:

'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) } }) })

这段代码的运行逻辑可以拆解为四个步骤,每一步都对应上文的核心 API:

  1. 取参:process.argv[2]为目录路径,process.argv[3]为扩展名,并提前拼接点号得到ext = '.' + process.argv[3]。
  2. 异步读取:调用fs.readdir(folder, callback),将目录中所有文件名一次性放入回调的list(此处命名files)参数。
  3. 错误处理:回调内先判断err,若出错立即console.error(err)并return,避免在空数组上继续操作。
  4. 过滤输出:用Array.prototype.forEach遍历files,凡path.extname(file) === ext的文件逐一console.log(file)——每行一个文件,正好满足“一行一个”的输出要求。

与官方解在写法上略有差异但同样正确的风格也很常见:先filter()再forEach(),例如测试用例 test/filtered_ls/valid_01.js 正是这种写法:

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 中,其验证策略分为三个阶段:

1. 构造真实的测试目录

addSetup阶段会在系统临时目录(os.tmpdir())下创建一个以进程 PID 命名的目录(_learnyounode_<pid>),然后依据 exercises/filtered_ls/file-list.json 中的文件清单逐个写入内容为nothing to see here的测试文件。该清单精心构造了多种边界情况,一共 15 个文件,其中包括:

[ "learnyounode.dat", "learnyounode.txt", "learnyounode.sql", "api.html", "README.md", "CHANGELOG.md", "LICENCE.md", "md", "data.json", "data.dat", "data.dat", "words.dat", "w00t.dat", "w00t.txt", "wrrrrongdat", "dat" ]

从源码结构看,这个清单设计的意图非常明确:既有.txt、.md、.dat等多种常规扩展名,也有md、dat、wrrrrongdat这类不带点号、恰好与扩展名同名的干扰项——如果程序只用endsWith()而不用path.extname(),就很容易被这些干扰项“误伤”。这从侧面印证了path.extname()才是正确的过滤方式。验证阶段传入的参数固定为md(即过滤出所有.md文件),期望输出仅为api.html、README.md、CHANGELOG.md、LICENCE.md之外……(实际上过滤md扩展名时,README.md、CHANGELOG.md、LICENCE.md命中)。

2. 包装 fs 模块以记录调用

验证器通过workshopper-wrappedexec包装子进程,并加载 exercises/my_first_io/wrap.js 作为运行时探针:该模块会遍历fs对象的所有方法,逐一替换为记录调用的代理函数,通过调用栈判断这些 fs 调用是否来自用户提交的主程序,并记入ctx.fsCalls。这样一来,无论用户程序内部调用的是同步还是异步方法,都会被如实登记。

3. 判定规则

addVerifyProcessor阶段遍历exercise.wrapData.fsCalls记录的所有被调用过的 fs 方法名:

  • 凡是方法名以Sync结尾(如fs.readdirSync),判定为违规使用同步 I/O,输出失败信息;
  • 其余方法视为异步调用,输出通过信息;
  • 如果用户程序压根没调用任何 fs 方法(既无同步也无异步),同样判定失败,这是为了防止用child_process.exec('ls ...')之类的手段绕过练习核心目的。

最终callback(null, usedAsync && !usedSync),即只有至少使用了一次异步 fs 调用、且完全没碰同步调用时才通过验证。对应地,测试目录中的反例也分两类:/test/filtered_ls/invalid_01.js 使用fs.readdirSync(同步 I/O),/test/filtered_ls/invalid_02.js 使用child_process.exec('ls -1 ...')(绕过 fs),两者都会被验证器拒收。

运行与提交验证

编写好filtered-ls.js后,可以在本仓库根目录下先手动运行一遍,确认输出符合预期:

# 手动运行:第一个参数为目录,第二个参数为扩展名 node exercises/filtered_ls/solution/solution.js /path/to/dir txt # 使用 learnyounode 内置的本地文档进行离线自学 # fs 模块文档:docs-nodejs/fs.html # path 模块文档:docs-nodejs/path.html

然后通过 learnyounode 的验证命令检查程序是否正确(对应 problem.md 末尾的说明):

learnyounode verify filtered-ls.js

验证通过后,本课程还会在下一题 make_it_modular(模块化改造) 中要求把同样的过滤逻辑抽成独立模块、通过回调返回结果——届时fs.readdir的异步回调模式会继续复用,配套的模块化验证器也位于 exercises/make_it_modular/verify.js。因此,扎实吃透本练习的异步思维,是后续进阶的基石。

常见错误自查清单

结合验证器规则与官方反例,提交前可对照以下清单自查:

检查项正确做法常见错误
参数下标从process.argv[2]开始取从process.argv[0]开始,误取到系统信息
扩展名处理用path.extname()或自行拼接.前缀直接用endsWith(ext),被md、dat等干扰项误命中
I/O 方式fs.readdir()(异步)使用fs.readdirSync()或child_process.exec('ls')绕过
错误处理回调内先判err忽略err导致后续空数组操作抛错
输出格式每个文件单独一行console.log(file)用数组join(' ')打在同一行

小结

filtered_ls 这道练习虽小,却浓缩了 Node.js 入门阶段最核心的三个基本功:process.argv命令行参数解析、fs.readdir()的异步回调模式、以及path.extname()对文件名结构的标准化处理。通过本文对官方参考答案(solution.js)与验证器源码(exercise.js)的逐层剖析,你不仅知道“怎么写能通过”,更理解了“验证器为什么能拦截同步写法”——这种对运行机制本身的理解,比单纯通关更有长期价值。

  • 教程
  • 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
点击查看免费下载
上一篇:7大核心组件构建实时数据处理管道:从数据风暴到决策洞察的全链路实践
下一篇:基于 agno 环境框架对比推理强度策略:Policy Settings 指南与测试实证

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 2:33:56

BaiduPCS-Go 下载速度调优全解:3 种账号配置一次到位

BaiduPCS-Go 下载速度调优全解&#xff1a;3 种账号配置一次到位 【免费下载链接】BaiduPCS-Go iikira/BaiduPCS-Go原版基础上集成了分享链接/秒传链接转存功能 项目地址: https://gitcode.com/GitHub_Trending/ba/BaiduPCS-Go BaiduPCS-Go 是一款基于 iikira 原版开发的…

作者头像 李华
网站建设 2026/9/28 2:33:28

Qt+C++扫雷实战:从环境搭建到状态机设计

简介&#xff1a;本资源是一份面向C初学者与高校程序设计课程学习者的可视化扫雷小程序完整实现源码&#xff0c;适用于《C程序设计》大作业实践与图形界面编程入门训练。项目基于Qt框架开发&#xff0c;包含15个核心文件&#xff1a;4个.cpp源文件&#xff08;含主窗口、游戏逻…

作者头像 李华
网站建设 2026/9/28 2:29:55

Python空气质量数据挖掘与机器学习可视化分析系统实战

简介&#xff1a;本资源面向环境科学、数据挖掘与机器学习方向的学习者与研究者&#xff0c;提供一套基于Python的空气质量数据可视化分析系统源码及配套数据&#xff0c;可用于城市群划分、污染传输网络构建与传播过程探索等课题实践。项目采用BS架构&#xff0c;前端整合HTML…

作者头像 李华
网站建设 2026/9/28 2:29:34

WinPcap实现UDP精确发包:绕过协议栈的源码与调试指南

简介&#xff1a;一份基于WinPcap实现的UDP发包程序源码包&#xff0c;面向网络编程初学者、协议分析爱好者及课程设计开发者。程序演示了如何借助WinPcap在驱动层完成UDP数据报的构造与发送&#xff0c;适合用作理解UDP无连接特性和底层封包机制的入门范例&#xff0c;也可延伸…

作者头像 李华