Go Doublestar 双星号嵌在普通文件名中为什么不是递归匹配
来源:17golang原创
时间:2026-10-29 14:00:15 408浏览 收藏
Go Doublestar 中,docs/**.md 的双星号和扩展名位于同一个文件名段,作用与 docs/*.md 相同,不会跨过路径分隔符递归匹配子目录。要匹配 docs 下任意深度的 Markdown 文件,应写成 docs/**/*.md:让 ** 单独占据一个路径段,后面的 *.md 负责匹配文件名。
官方地址:https://github.com/bmatcuk/doublestar
下面围绕 v4 导入路径说明这一处语法差异。先区分模式的含义,再处理路径基准,避免把合法但含义不同的模式误判成库失效。
先检查双星号是否占据独立路径段
排查时按 / 把模式拆开。docs/**.md 只有 docs 和 **.md 两段;后者仍是一个文件名模式。星号数量增加,并不意味着这一段能够吞掉中间的目录分隔符。docs/**/*.md 则把目录层级和文件扩展名拆成了不同职责。
| 模式 | 关注范围 | 目录深度 |
|---|---|---|
docs/*.md | docs 的直接子项名称 | 不跨目录 |
docs/**.md | 与上一行相同的文件名范围 | 不跨目录 |
docs/**/*.md | docs 内符合名称条件的路径 | 零层或更多目录 |
**/ 可以覆盖零层目录,因此递归写法也能匹配 docs/readme.md,不要求文件必须放进一层子目录。需要恰好一层子目录时,应表达为 docs/*/*.md,不能用递归模式代替深度限制。

用直接子文件和深层文件分开判断
只拿一个 docs/readme.md 判断递归是否有效,证据不足,因为三种写法都可能覆盖它。应至少保留一个直接子文件、一个嵌套子文件和一个扩展名不同的文件。由模式规则可推导:两种文件名段写法可以匹配 readme.md,但无法匹配 guide/setup.md;递归写法覆盖这两个 Markdown 路径,不覆盖 setup.txt。
下面是完整示例,使用标准库的内存文件系统固定路径集合。代码用于读者自行复现,文中的判断来自官方模式规则,不是本次运行日志。
package main
import (
"fmt"
"log"
"testing/fstest"
"github.com/bmatcuk/doublestar/v4"
)
func main() {
// 内存文件树用于说明模式含义,不依赖当前工作目录。
files := fstest.MapFS{
"docs/readme.md": &fstest.MapFile{Data: []byte("root")},
"docs/guide/setup.md": &fstest.MapFile{Data: []byte("child")},
"docs/guide/setup.txt": &fstest.MapFile{Data: []byte("text")},
}
patterns := []string{"docs/**.md", "docs/**/*.md"}
names := []string{"docs/readme.md", "docs/guide/setup.md"}
for _, pattern := range patterns {
for _, name := range names {
// Match 只比较字符串,不读取文件是否存在。
matched, err := doublestar.Match(pattern, name)
if err != nil {
log.Fatal(err)
}
fmt.Printf("pattern=%q name=%q matched=%t\n", pattern, name, matched)
}
// Glob 在给定文件系统内查找;模式相对于 files 的根目录。
matches, err := doublestar.Glob(files, pattern)
if err != nil {
log.Fatal(err)
}
fmt.Printf("pattern=%q files=%v\n", pattern, matches)
}
}
依赖为 github.com/bmatcuk/doublestar/v4,可以放入已有 Go module,并通过项目的依赖管理方式引入。示例没有读取本机目录,也没有把输出内容当成实际扫描结果。若业务目录可能存在同名目录,注意 *.md 是名称条件,本身并不承诺结果一定是普通文件;需要排除目录时可在 Glob 中使用 WithFilesOnly(),并单独考虑符号链接规则。
再检查匹配的路径基准和 API
Match 比较模式和整个 name 字符串,不负责读取磁盘。即使某个路径不存在,只要字符串符合规则,也可以返回 true。不要在传入 Match 前只保留 Base 文件名,否则原来想判断的目录层级已经丢失。
Glob 则相对于传入的 fs.FS 根查找。假设使用 os.DirFS("docs"),模式就应是 **/*.md;若根是 docs 的父目录,才写 docs/**/*.md。把 docs 同时写进根目录和模式,实际查询的层级就变了。

Match 和 Glob 使用正斜杠作为分隔符。处理操作系统原生路径时,可以考虑 PathMatch 或 FilepathGlob;后者会整理路径并拆分基础路径。filepath.ToSlash 只替换当前平台识别的路径分隔符,不是任意外部路径格式的通用解析器。来自不同平台的配置数据应先约定格式。
错误为空并不表示模式符合递归意图
docs/**.md 是合法模式,所以不会仅因它没有递归含义就报错。err == nil 表示这次调用没有报告错误,并不表示查找范围符合业务预期。Glob 没有匹配项时可以返回空结果;默认还会忽略目录读取等文件系统错误,因此也不能单靠空集合断言目录里没有文件。
如果调用方需要感知读取错误,可按需求传入 WithFailOnIOErrors()。但一个不存在的固定路径默认仍被当成无匹配,相关固定前缀不存在的处理还应结合 WithFailOnPatternNotExist() 的具体语义。不要通过无条件扩大递归范围来掩盖根目录设置错误。
收尾检查清单与常见疑问
- 确认是
**/*.md,而不是把 ** 与后缀连成一个文件名段。 - 同时比较根层和嵌套层样本,避免只看直接子文件。
- 确认传入的是完整相对路径,并且模式与 fs.FS 根目录相配。
- 区分“字符串匹配成功”“查找到文件”和“读取错误未报告”。
- 将递归范围限定在目标根目录,文件类型条件另外表达。
双星号越多,递归层数越多吗?不能这样理解。这里的关键是 ** 的独立路径段位置,不是星号数量对应目录层数。
递归写法会漏掉 docs 直属文件吗?不会因缺少中间目录而漏掉,因为该目录段允许零层。若仍为空,应回到路径基准和文件名条件排查。
能把 glob 当正则表达式吗?不能。先使用库支持的 glob 语法表达路径范围,额外筛选规则可另行处理,不要混入正则语义。
-
329 收藏
-
494 收藏
-
422 收藏
-
446 收藏
-
402 收藏
-
496 收藏
-
397 收藏
-
389 收藏
-
476 收藏
-
138 收藏
-
229 收藏
-
397 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习