MacOS与Windows下fs.readdirSync文件名匹配失效问题
问题原因
这个问题是跨平台文件名的Unicode 规范化规则不一致导致的,和JS逻辑本身无关:
- macOS 采用的 APFS/HFS+ 文件系统,默认强制使用 NFD(分解式规范化)存储文件名:带变音符号的字符会被拆成「基础拉丁字符 + 独立组合变音标记」两个Unicode码点存储,例如
ä会被拆为基础字符a(U+0061)+ 组合分音符(U+0308)两个码点。 - Windows 采用的 NTFS 文件系统,默认使用 NFC(预组合式规范化)存储文件名:带变音符号的字符会作为单个独立Unicode码点存储,例如
ä直接对应单个码点U+00E4。
JS的Array.includes()对字符串的匹配是严格逐码点比对的,哪怕两个字符串渲染后视觉完全一致,只要底层的Unicode码点序列不同,就会判定为不相等。即使两个文件夹都在macOS环境下,只要文件来源不同(比如一个是从Windows设备拷贝而来,一个是macOS本地新建),文件名的编码形式就可能存在差异,最终导致匹配失败。
修复方法
读取文件名后,统一调用JS内置的String.prototype.normalize()方法,将所有文件名转换为同一种Unicode规范化形式再做比对即可,跨平台场景推荐统一转为NFC形式,和绝大多数系统、软件的默认处理逻辑对齐。
修正后的可运行代码:
import fs from "fs"; const dir = "../path/"; // 读取文件名后统一做NFC规范化处理 const files = fs.readdirSync(`${dir}folder1/`).map(fileName => fileName.normalize('NFC')); const filesDone = fs.readdirSync(`${dir}folder2/`).map(fileName => fileName.normalize('NFC')); console.log(files.length, filesDone.length); const unMatchedFiles = files.filter(val => !filesDone.includes(val) && val.endsWith('.json')); console.log(unMatchedFiles); console.log(filesDone);
注意:不要通过手动枚举替换特殊字符的方式处理匹配问题,该方案覆盖场景有限容易遗漏,
normalize()是ES标准内置方法,所有支持ES6的Node.js版本均可直接使用,可覆盖所有Unicode特殊字符的规范化场景。
内容的提问来源于stack exchange,提问作者psquizzle
相关产品推荐
相关产品推荐

