☰
node-glob 完全指南:Bash 语义的 glob 匹配、API 家族全景与源码级原理剖析
2026/9/25 5:21:39 网站建设 项目流程
  • 开发工具

【免费下载链接】node-glob

glob functionality for node.js

项目地址:https://gitcode.com/gh_mirrors/no/node-glob
点击查看免费下载

本篇技术指南以当前仓库 README.md 为主体,系统讲解glob(node-glob)这个 Node.js 原生 glob 匹配库的完整使用面:从安装与五种函数形态(异步 / 同步 / 流 / 迭代器 / 对象),到GlobOptions全部配置项的参数语义,再到*、?、[...]、extglob、globstar 与花括号展开的模式语法,最后以源码实现(src/glob.ts、src/pattern.ts、src/walker.ts、src/ignore.ts)佐证其行为差异、Windows 平台约定与性能基准。读完你将能够根据场景准确选择 API、精准写出与 Bash 语义一致的模式、正确配置跨平台与性能相关选项,并理解其内部的路径解析与遍历机制。


一、快速上手:安装与包名澄清

glob通过 npm 安装,包名就是glob:

npm i glob

[!NOTE] 包名不是node-glob。node-glob是多年前就已被遗弃的另一个包,直接安装glob即可。本仓库即该包的源码镜像,当前版本为13.0.6(见 package.json),声明支持node: 18 || 20 || >=22。

模块支持 ESM 与 CommonJS 两种加载方式(src/index.ts 通过 tshy 同时产出dist/esm与dist/commonjs两套构建):

// load using import import { glob, globSync, globStream, globStreamSync, Glob } from 'glob' // or using commonjs, that's fine, too const { glob, globSync, globStream, globStreamSync, Glob, } = require('glob')

glob()与globSync()是核心入口,分别返回Promise<string[]>与string[](开启withFileTypes时返回Path[])。最基础的用法:

// all js files, but don't look in node_modules const jsfiles = await glob('**/*.js', { ignore: 'node_modules/**' })

二、五种 API 形态:数组、流、迭代器与可复用对象

从 src/index.ts 可以看到,所有顶层函数都是对Glob类的薄封装(如globSync内部就是new Glob(pattern, options).walkSync())。因此五种形态本质是同一遍历引擎的四种出口。

1.glob(pattern, options?) => Promise<string[] | Path[]>

异步搜索,返回withFileTypes:true时为Path对象数组。支持多模式数组、AbortSignal取消:

// pass in a signal to cancel the glob walk const stopAfter100ms = await glob('**/*.css', { signal: AbortSignal.timeout(100), }) // multiple patterns supported as well const images = await glob(['css/*.{png,jpeg}', 'public/*.{png,jpeg}'])

2.globSync(pattern, options?) => string[] | Path[]

同步版本,签名与glob()完全一致:

// but of course you can do that with the glob pattern also // the sync function is the same, just returns a string[] instead // of Promise<string[]> const imagesAlt = globSync('{css,public}/*.{png,jpeg}')

3.globStream / globStreamSync => Minipass<string | Path>

流式输出,返回一个 minipass 流,全部匹配发出后触发end。同步流的特点是"消费多快读多快"——立即消费时甚至可在单个 tick 内全部读完,但消费不及时仍会响应背压(backpressure)。该语义在 src/index.ts 的函数注释中有明确说明:

// you can also stream them, this is a Minipass stream const filesStream = globStream(['**/*.dat', 'logs/**/*.log'])

4.globIterate / globIterateSync => (Async)Generator<string>

迭代器形态,适用于for await或for...of惰性消费:

const g = new Glob('**/foo', {}) // glob objects are async iterators, can also do globIterate() or // g.iterate(), same deal for await (const file of g) { console.log('found a foo file:', file) }

glob主函数对象上还挂了一组别名,见 src/index.ts:glob.sync()、glob.stream()、glob.iterate()、glob.sync.stream()、glob.sync.iterate()、glob.stream.sync()等,全部指向同一实现。

5.hasMagic(pattern, options?) => boolean

判断模式是否包含"魔幻字符"。花括号展开不算 magic,除非显式设置magicalBraces——因为{a,b}只是把字符串展开成数组,'x{a,b}y'等价于对['xay', 'xby']分别判断,两者都不含 magic。源码见 src/has-magic.ts,实现就是逐个对模式调用new Minimatch(p, options).hasMagic()。

6.escape / unescape

escape(pattern)转义所有魔幻字符使其只匹配字面量;unescape(pattern)反向还原。二者在 src/index.ts 中从minimatch直接再导出。行为与windowsPathsNoEscape相关:

  • 未设置windowsPathsNoEscape时:unescape同时移除花括号转义与反斜杠转义;escape用\转义。
  • 设置后:escape改为用[]包裹魔幻字符(字符类中只能匹配该精确字符),unescape只移除方括号转义、不动反斜杠(此时\是路径分隔符),例如'[*]'还原为*,但'\\*'不会还原为'*'。
  • 斜杠(以及windowsPathsNoEscape模式下的反斜杠)永远不可被转义或反转义。

三、Glob类:缓存复用的遍历对象

Glob是唯一真正执行遍历的类,构造函数要求传入 options:

const g = new Glob(pattern: string | string[], options: GlobOptions)

遍历方法(可重复调用)

方法返回
g.walk()Promise,解析为结果数组
g.walkSync()结果数组
g.stream()异步流(Minipass)
g.streamSync()同步流
g.iterate()默认异步迭代,返回AsyncGenerator
g.iterateSync()默认同步迭代,返回Generator

Glob实现了[Symbol.iterator]/[Symbol.asyncIterator](src/glob.ts),所以for await (const f of g)与for (const f of g)都直接可用。

属性

  • g.opts:构造函数收到的原始 options。
  • g.patterns:解析后的不可变Pattern对象数组。
  • 所有已解析的选项同时作为属性挂在对象上(g.dot、g.nocase、g.maxDepth等)。

缓存复用:把Glob当作 options 传

遍历同一目录树多次时,把前一个Glob实例直接作为下一个实例的 options,可复用其PathScurry目录遍历缓存,显著提速(src/glob.ts):

// pass a glob as the glob options to reuse its settings and caches const g2 = new Glob('**/bar', g) // sync iteration works as well for (const file of g2) { console.log('found a bar file:', file) }

注意 src/glob.ts 中构造器第一行:if (!opts) throw new TypeError('glob options required')——options 是必传的,即使用空对象{}也要给。

withFileTypes:true的Path对象

开启后匹配结果是path-scurry的Path对象,类似fs.Dirent但更强(含fullpath()、relative()、isDirectory()、readdirSync()等方法):

const g3 = new Glob('**/baz/**', { withFileTypes: true }) g3.stream().on('data', path => { console.log( 'got a path object', path.fullpath(), path.isDirectory(), path.readdirSync().map(e => e.name), ) })

withFileTypes与absolute互斥——src/glob.ts 中同时设置会直接抛错:'cannot set absolute and withFileTypes:true'。

四、GlobOptions全参数详解

options 以 TypeScript 接口GlobOptions导出,可传给任何函数形态;除特别注明外全部可选、布尔型、默认false。以下按功能域分组逐项说明,默认值与冲突约束均与 src/glob.ts 源码一致。

4.1 路径与平台

参数默认值说明
cwdprocess.cwd()搜索起点。可为字符串路径、file://字符串或 URL 对象;构造器内通过fileURLToPath()归一化(src/glob.ts)
root无相对于cwd解析,作为/开头绝对模式的起点。注意:并不会把遍历限制在 root 内——含..的模式仍可越出 root,非绝对模式仍在cwd中匹配。例如/../*配{root:'/some/path'}返回/some下所有文件,*配{root:'/some/path'}返回 cwd 下条目。想同时让绝对与非绝对模式同起点可用{root:''};Windows 上x:/*或//host/share/*永远从对应盘符/共享目录开始
platformprocess.platform,缺失时'linux'在非 Windows 系统设'win32'可能产生奇怪行为
posixfalse结果强制用/分隔。POSIX 上无效果;Windows 上绝对路径以完整 UNC 形态返回,如//?/C:/foo/bar而非'C:\\foo\\bar'
absolute未设置时:绝对模式返回绝对路径,其余返回相对cwd的路径true恒返回绝对路径,false恒返回相对路径。只做字符串路径解析,不会额外系统调用;不可与withFileTypes同时使用
dotRelativefalse给相对路径加./(Windows 为.\)前缀;默认返回"裸"相对路径'foo/bar'。以../开头的模式即使开启也不加前缀
windowsPathsNoEscapefalse\\只作为路径分隔符、永不作转义字符,模式中所有\\被替换为/。代价是无法匹配文件名中的字面 glob 字符,但允许用path.join()/path.resolve()构造的模式在 Windows 上匹配(复刻 v7 及以前版本的"buggy"行为)。历史原因:allowWindowsEscape恰好设为false时也等价开启(该旧选项已废弃)

4.2 匹配行为

参数默认值说明
dotfalse让.dot文件参与普通匹配与 globstar 匹配。模式中显式的点(如a/.*/c里的.)永远能匹配点文件
nocasemacOS/Windows 为true,其余false大小写不敏感。只有确认文件系统大小写敏感性与平台默认不一致时才应显式设置。源码层面,Glob会用字符串片段原样直传readdir()来避免大量 RegExp 创建(src/glob.ts):在大小写敏感文件系统上开nocase:true,Foo/*会读不到FOO/目录;在大小写不敏感系统上关掉它,Foo/*会匹配foo/bar但按模式中的大小写报告为Foo/bar。默认值通常正确,挂载了与宿主不同敏感性的文件系统时才需手动调整
matchBasefalse模式不含斜杠时只按 basename 匹配,*.js等价于**/*.js。开启时若同时设noglobstar,src/glob.ts 会抛TypeError('base matching requires globstar')
maxDepthInfinity限制遍历相对cwd的最大目录深度
nodirfalse不匹配目录只匹配文件;想只匹配目录就在模式结尾加/。follow与nodir同时开启时指向目录的符号链接也会被排除
markfalse给目录匹配结果追加/(Windows 为\),需要额外 stat 调用
nobracefalse不展开{a,b}、{1..3}花括号集合
noglobstarfalse不让**匹配多层文件名,视为普通*
noextfalse不匹配+(a|b)这类 extglob 模式
magicalBracesfalse把{a,b}花括号视为"magic"。仅影响hasMagic(),不影响匹配本身;nobrace开启时无效

4.3 结果增强与性能

参数默认值说明
statfalse对所有条目调用lstat()。与withFileTypes配合时,匹配结果携带 mtime、权限等统计字段;代价是额外系统调用
realpathfalse对结果调用fs.realpath,无法解析的条目被丢弃;同样有性能开销
withFileTypesfalse返回Path对象而非字符串;不可与absolute共用

stat:true+withFileTypes:true的典型用法——排序、过滤权限位:

const results = await glob('**', { stat: true, withFileTypes: true }) const timeSortedFiles = results .sort((a, b) => a.mtimeMs - b.mtimeMs) .map(path => path.fullpath()) const groupReadableFiles = results .filter(path => path.mode & 0o040) .map(path => path.fullpath())

4.4 过滤、取消与剪枝

ignore:接受字符串、字符串数组,或带ignored/childrenIgnored方法的对象。

  • 字符串/数组形式按 glob 模式排除;要连目录本身及其全部子级一起忽略,在模式末尾加'/**'。
  • 对象形式由方法按Path决定是否排除/是否剪枝其子树:
// custom ignores can be done like this, for example by saying // you'll ignore all markdown files, and all folders named 'docs' const customIgnoreResults = await glob('**', { ignore: { ignored: p => /\.md$/.test(p.name), childrenIgnored: p => p.isNamed('docs'), }, })

对象的方法签名在 src/ignore.ts 的IgnoreLike接口中定义。字符串/数组形式由内置Ignore类处理(src/ignore.ts),其实现会同时编译相对与绝对、以及"是否以/**结尾(children 模式)"四组 Minimatch 匹配器。注意ignore 模式永远按dot:true解析,不受其他设置影响。

signal:传入AbortSignal,触发即取消遍历(src/walker.ts 中注册 abort 监听)。典型用法见上文AbortSignal.timeout(100)。

follow:让**遍历符号链接目录。默认(同 Bash):**不在模式首位时最多跟随 1 个符号链接,位于首位则跟随 0 个。开启后循环链接可能产生大量重复引用并拖垮性能。配合nodir时指向目录的符号链接同样被排除。

includeChildMatches(默认true):false时不再匹配任何已匹配项的子孙。例如**\/foo会匹配a/foo但不匹配a/foo/b/foo。典型场景是"找出所有node_modules,但不含node_modules里的"。其机制是匹配命中后把相对路径/**追加进 ignore(src/walker.ts),因此要求 Ignore 实现具备add()方法,否则抛错。有两个使用前提:

  1. 它只忽略"在某祖先命中之后才被匹配到的后代";由于文件系统遍历顺序不确定,多模式或花括号模式中后代可能先于祖先被发出。
  2. 只在能确定模式各组件不会互为子孙、或偶尔混入子孙条目无碍时才关闭:
const results = await glob( [ // likely to match first, since it's just a stat 'a/b/c/d/e/f', // this pattern is more complicated! It must to various readdir() // calls and test the results against a regular expression, and that // is certainly going to take a little bit longer. // // So, later on, it encounters a match at 'a/b/c/d/e', but it's too // late to ignore a/b/c/d/e/f, because it's already been emitted. 'a/[bdf]/?/[a-z]/*', ], { includeChildMatches: false }, )

4.5 底层扩展与调优

参数默认值说明
fs无自定义文件系统实现覆盖,可注入内存文件系统等;能力范围见path-scurry的FSOption。仓库测试 test/custom-fs.ts 与 test/memfs.ts 演示了自定义 fs / 内存 fs 场景
scurry自动按平台创建预置的PathScurry对象。若显式设置nocase,传入的scurry必须与其一致,否则 src/glob.ts 抛'nocase option contradicts provided scurry option'。构造器按平台选择PathScurryWin32/PathScurryDarwin/PathScurryPosix(src/glob.ts)
braceExpandMax10_000{x,y,...}展开数量的上限。超过即有 OOM 风险。注意 src/glob.ts 中构造器硬编码了braceExpandMax: 10_000,这远低于 minimatch 自身的100_000默认值,因为 glob 还要遍历文件系统树、内存占用更高
debugfalse透传给 Minimatch,使所有匹配变慢且极度啰嗦

五、Glob Primer:模式语法全解

"glob" 就是你在命令行敲ls *.js或写进.gitignore的build/*那种模式。解析前,花括号段先展开成集合:以{开始、}结束、内部含 2 个以上逗号分隔段;花括号段可以含斜杠,如a{/b/c,bcd}展开为a/b/c与abcd。

除**外,以下魔幻字符都不匹配路径分隔符(所有平台上的/,以及 Windows 上的\):

字符语义
*匹配单个路径段内 0 个或多个字符;单独成段时至少匹配 1 个字符;未开dot:true时不匹配段首的.
?匹配 1 个字符;未开dot:true时不匹配段首的.
[...]类似 RegExp 的字符范围;首字符为!或^时匹配补集;首字符为]时视为\]而非字符类结束
!(p1\|p2\|p3)不匹配任何给定模式;不得包含/;单独成段时至少需 1 个字符
?(p1\|p2\|p3)匹配给定模式的 0 或 1 次;不得含/
+(p1\|p2\|p3)匹配给定模式的 1 次及以上;不得含/
*(a\|b\|c)匹配给定模式的 0 次及以上;不得含/
@(p1\|p2\|p3)恰好匹配其中一个;不得含/
**单独成段时为 globstar:匹配 0 个或多个目录层级。不爬取符号链接目录(除非follow:true);a/b/**仅当a/b是目录时才匹配它;不在模式首位时默认跟随 1 个符号链接、首位时跟随 0 个

[:class:]形式的 POSIX 命名字符类受支持,且是 Unicode 感知的;但[=c=](区域字符排序权重)与[.symbol.](排序符号)不支持。

Dots:点文件规则

路径段首字符是.时,除非模式对应段首也是.,否则不匹配任何模式。例如a/.*/c能匹配a/.b/c,而a/*/c不能——*不以点开头。设置dot:true可让点文件按普通字符参与匹配。

Basename Matching

matchBase:true且模式无斜杠时,会在整棵树任意层级按 basename 查找,*.js可匹配test/simple/basic.js(参见测试 test/match-base.ts)。

空集合行为

无匹配时返回空数组(或立即结束的流)。这与 shell 不同——shell 会原样回显模式:

$ echo a*s*d*f a*s*d*f

glob没有nonull选项:模式无匹配就是无匹配。

六、与其他 fnmatch/glob 实现的差异

以下是 node-glob 与其他实现刻意保留的差异(详见 README.md 与对应测试,如 test/bash-comparison.ts):

  • **默认启用(除非noglobstar):与 bsdglob 和 bash 5 一致,**只在独占整个路径段时有特殊含义。a/**/b匹配a/x/y/b,但a/**b不匹配。符号链接目录不会被**遍历(内容可能被后续模式段匹配),防止死循环与重复;{follow:true}可强制遍历。
  • 无nonull:无匹配即空结果。
  • 花括号先展开:若未禁用,花括号在任何其他解释之前展开。因此 bash/zsh 中非法的+(a|{b),c)}会先展开为+(a|b)与+(a|c)两个合法模式继续匹配。
  • POSIX 字符类:支持[:class:]且 Unicode 感知,不支持[=c=]与[.symbol.]。
  • 重复斜杠合并:与 Bash/zsh 不同,重复/一律合并为单个分隔符(src/pattern.ts 的Pattern即基于/连接的 glob parts 构建)。
  • 注释与取反已移除:以#开头的"注释模式"与!开头的"取反模式"在 v5 废弃、v6 移除。需要排除匹配请使用ignore选项。

七、Windows 平台行为

glob 表达式中请只使用正斜杠/。虽然 Windows 的路径分隔符是/或\,但本实现只认/;反斜杠一律按转义字符解释,而非路径分隔符。因此:

  • 绝对模式如/foo/*的结果通过path.join挂到 root 设置上,Windows 下默认表现为C:\foo\bar.txt。
  • 想自动把所有\强制成/(从而无法转义字面 glob 字符),设windowsPathsNoEscape:true。

Windows、CWD、盘符与 UNC 路径

  • POSIX:模式以/开头时忽略cwd,从/加模式中的非魔幻路径段开始遍历。
  • UNC 路径:Windows 上模式可直接以 UNC 开头。//?/x:/*返回x:盘根下所有条目;//ComputerName/Share/*返回对应共享的全部文件。UNC 根比较恒为大小写不敏感。
  • 盘符:c:/*无论cwd如何都在c:盘搜索。模式以/开头(非 UNC)且显式设置了带盘符的cwd时,用该盘符作为遍历根:glob('/tmp', { cwd: 'c:/any/thing' })返回['c:/tmp']。未显式提供cwd且模式以/开头时,遍历运行在cwd所在盘符的根(即path.resolve('/')的结果)。

相关行为有专门测试覆盖:test/windows-paths-fs.ts、test/windows-paths-no-escape.ts、test/slash-cwd.ts。

八、竞态条件与缓存

glob 本质上依赖目录遍历,天然存在竞态:文件可能在 glob 检查到时存在、返回结果时已被删除或修改。本实现为降低系统开销会缓存所有 readdir 调用,这在 Glob 对象复用(缓存跨多次调用共享)时使竞态更明显。因此不要把一个 glob 结果当作快速变化文件系统状态的保证——绝大多数场景下这不是问题。

九、命令行工具:glob-bin

自 v13 起,glob 的 CLI 已迁移到独立的glob-bin包,需另行安装:

npm install glob-bin

十、性能基准与生态对比

README 明确将本模块定位为"the most correct and second fastest glob implementation in JavaScript"(在尽量忠实 Bash 模式展开语义的前提下追求最快)。作者在 README 中记录了自己在本仓库 benchmark.sh 与 make-benchmark-fixture.sh 配套基准集上的实测数据(20 万文件、嵌套 4 层的目录树,时间越小越好,第二列为结果数量):

--- pattern: '**' --- ~~ sync ~~ node fast-glob sync 0m0.598s 200364 node globby sync 0m0.765s 200364 node current globSync mjs 0m0.683s 222656 node current glob syncStream 0m0.649s 222656 ~~ async ~~ node fast-glob async 0m0.350s 200364 node globby async 0m0.509s 200364 node current glob async mjs 0m0.463s 222656 node current glob stream 0m0.411s 222656 --- pattern: '**/..' --- ~~ sync ~~ node fast-glob sync 0m0.486s 0 node globby sync 0m0.769s 200364 node current globSync mjs 0m0.564s 2242 node current glob syncStream 0m0.583s 2242 ~~ async ~~ node fast-glob async 0m0.283s 0 node globby async 0m0.512s 200364 node current glob async mjs 0m0.299s 2242 node current glob stream 0m0.312s 2242

(其余**/*.txt、**/[0-9]/**/*.txt、extglob、深层../回溯等十余组模式的完整数据见 README.md。)

需要说明:以上为 README 中记录的作者单次基准输出,属历史快照;具体数值会随机器与版本变化。README 给出的选择建议是:

  • 追求与 Bash 语义尽可能一致、在此约束下尽可能快 → 使用本模块。
  • 模式相对简单、追求绝对最快 → 使用fast-glob(作者自述其约快 10–20%,但在部分场景与 Bash 结果不一致,例如**只匹配文件不匹配目录、..路径段仅支持出现在模式开头、!(9).txt不会匹配9999.txt、中段花括号可能漏匹配、extglob 允许含/)。
  • 模式简单且希望自动尊重.gitignore→ 使用globby(基于 fast-glob 的包装,支持 ignore 文件与!取反模式,作者测得约比本模块慢 10–20%)。

注意:node-globv7 及以前的旧版本不在该对比列表中——旧版 API 过时且性能不适合任何性能敏感场景。本仓库的 Logo 文件(glob字符拼成的卡通图)位于 logo/ 目录。

十一、开发与测试约定

README 的贡献规范为:任何行为变更(含 bugfix)都必须附带测试,跑不过测试或降低性能的补丁会被拒绝。仓库提供的命令(详见 package.json):

# to run tests npm test # to re-generate test fixtures npm run test-regen # run the benchmarks npm run bench # to profile javascript npm run prof

测试覆盖了 README 中几乎每个选项与边界:dot(test/dot-relative.ts)、nocase(test/nocase-magic-only.ts)、follow(test/follow.ts)、ignore(test/ignore.ts)、includeChildMatches(test/include-child-matches.ts)、mark(test/mark.ts)、matchBase(test/match-base.ts)、maxDepth(test/max-depth.ts)、escape(test/escape.ts)、hasMagic(test/has-magic.ts)以及AbortSignal取消(test/signal.ts)等。若要深入某个选项的实现,src/glob.ts 的构造器是选项解析的枢纽,src/walker.ts 是遍历与匹配校验的引擎,src/ignore.ts 与 src/pattern.ts 则分别承担忽略逻辑与模式段的切分/判定。


总结:node-glob 的核心价值在于"正确性优先、速度次之"——以 Bash 模式展开语义为基准,通过Glob+PathScurry的目录缓存机制在忠实语义下做到高性能。掌握本文的五种 API 形态、GlobOptions的参数约束(尤其是absolute与withFileTypes、matchBase与noglobstar的互斥)、glob 模式语法与 Windows 正斜杠约定,即可在构建工具、文件收集器、代码生成器等场景中写出既正确又可控的路径匹配代码。

  • 开发工具

【免费下载链接】node-glob

glob functionality for node.js

项目地址:https://gitcode.com/gh_mirrors/no/node-glob
点击查看免费下载
上一篇:5分钟掌握VidBee:小白也能上手的全网视频下载终极指南
下一篇:Liger-Kernel终极模型适配指南:从LLaMA到Qwen的完整支持清单

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

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

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

立即咨询