登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

filepathGlob匹配分层文件名

来源:17golang原创

时间:2026-11-15 06:16:45 284浏览 收藏

Go 里用 filepath.Glob 匹配分层文件名时,最容易误判的是通配符的范围。比如 reports/*/*.json 只表示“reports 下的一层目录,再接一个 JSON 文件”,不会继续穿过第二层目录。目录层级固定时,直接把层级写进模式;层级不固定时,就用 filepath.WalkDir 遍历,再对文件名或相对路径做过滤。

官方文档:https://pkg.go.dev/path/filepath#Glob

filepath.Glob 固定目录层级的静态关系图
说明图:固定模式把 Glob、目录层级、通配符和 JSON 文件边界连接起来;它解释的是静态匹配关系,不是运行截图。

先把分层路径翻译成固定模式

把真实目录先写成树,再把每一段替换成模式,通常比直接试写通配符更稳。假设目录如下:

reports/
├── 2026/
│   ├── jan.json
│   └── feb.json
└── archive/
    └── 2025/
        └── dec.json

如果只想匹配 reports/2026/jan.json 这一层,可以使用 reports/*/*.json。这里第一个 * 匹配 2026,第二个 * 匹配 jan 或 feb。* 匹配的是不含路径分隔符的字符序列,因此它不会把 archive/2025 当成一个整体。

如果目标是 reports/archive/2025/dec.json,就要把两层目录明确写出来:reports/*/*.json 不能同时覆盖这条路径;可以使用 reports/*/*/*.json,但这会把层级固定为两层目录。filepath.Glob 没有 Bash ** 那种递归通配符语义。

用 Glob 读取结果并处理两类返回

Glob 返回匹配路径切片和错误。没有文件匹配时,结果是 nil 或空切片语义,不能当成系统故障;模式语法错误时,才会得到 filepath.ErrBadPattern。另外,遍历目录时发生的普通文件系统读取错误会被 Glob 忽略,所以它适合“尽力收集固定模式文件”,不适合要求报告每一个目录读取失败的场景。

package main

import (
	"errors"
	"fmt"
	"path/filepath"
)

func collectReports(root string) ([]string, error) {
	// 模式固定为:root 下的一层目录,目录中再找 JSON 文件。
	pattern := filepath.Join(root, "*", "*.json")
	matches, err := filepath.Glob(pattern)
	if err != nil {
		// 只有模式格式错误等 ErrBadPattern 才会从 Glob 返回。
		if errors.Is(err, filepath.ErrBadPattern) {
			return nil, fmt.Errorf("报告匹配模式无效: %w", err)
		}
		return nil, err
	}
	if len(matches) == 0 {
		// 无匹配是正常结果,由调用方决定是否降级或提示。
		return []string{}, nil
	}
	return matches, nil
}

这个函数把“无匹配”转换成空切片,把坏模式保留为错误。生产代码还可以根据业务需要排序;不要把返回顺序当成文件名排序契约。

为什么多写一层就匹配不到

问题通常不是 Glob 失效,而是模式表达的层数和目录真实层数不同。reports/*/*.json 的路径段可以拆成:固定的 reports、一个目录通配符、一个文件名通配符。如果真实文件是 reports/archive/2025/dec.json,中间多了一个目录段,模式自然不会命中。

Glob 与 WalkDir 的匹配边界静态关系图
说明图:固定层级模式与任意深度遍历是两种不同的结构关系;看左侧的 Glob 边界和右侧的 WalkDir、DirEntry、相对路径过滤关系。

不要用连续星号掩盖这个差异。Go 的 filepath.Match 规则里,* 不匹配路径分隔符;Glob 使用同一套模式语法。若需求是“所有后代目录中的 JSON”,应该明确换成遍历方案。

用 WalkDir 覆盖任意深度目录

WalkDir 适合层级不固定的目录树。遍历时先跳过目录项,再只对文件名做 filepath.Match("*.json", d.Name()),这样不会把路径分隔符交给文件名模式处理。下面的函数会收集任意深度下的 JSON 文件,并把遍历错误原样返回。

package reports

import (
	"fmt"
	"io/fs"
	"path/filepath"
)

func collectJSON(root string) ([]string, error) {
	var matches []string
	err := filepath.WalkDir(root, func(path string, d fs.DirEntry, walkErr error) error {
		// 目录读取失败要向上返回,不能把它伪装成“没有匹配”。
		if walkErr != nil {
			return walkErr
		}
		if d.IsDir() {
			return nil
		}
		matched, err := filepath.Match("*.json", d.Name())
		if err != nil {
			return fmt.Errorf("文件名模式无效: %w", err)
		}
		if matched {
			matches = append(matches, path)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return matches, nil
}

这里的取舍很明确:Glob 的代码短、边界固定、适合已知层级;WalkDir 能覆盖更深的目录,但要承担遍历成本,并自行决定忽略目录、权限错误和排序策略。

把模式选择收敛到一个收集入口

规模稍大后,最容易出现的是调用方各自拼接模式,最后有人用 Glob、有人手写递归,返回结果的错误语义也不一致。可以把固定层级和任意深度拆成两个明确入口:

package reports

import (
	"path/filepath"
	"sort"
)

func collect(root string, recursive bool) ([]string, error) {
	if !recursive {
		// 固定一层目录时使用 Glob,表达式直接反映目录契约。
		return filepath.Glob(filepath.Join(root, "*", "*.json"))
	}
	files, err := collectJSON(root)
	if err != nil {
		return nil, err
	}
	// 遍历顺序不作为 API 契约,统一排序便于后续生成批次。
	sort.Strings(files)
	return files, nil
}

调用方只需要决定目录层级是否固定,不必重复理解 Glob 的通配符边界。若文件集合很大,还可以在 WalkDir 回调里做前缀、扩展名或相对深度过滤,减少后续处理量。

几个容易踩到的边界

  • 只匹配文件名:filepath.Match("*.json", d.Name()) 不会检查完整路径;需要完整相对路径时,应先用 filepath.Rel 得到相对路径,再按固定层级设计模式。
  • 操作系统分隔符:文件系统路径用 filepath.Join 组合;不要把 URL 的斜杠规则和本地路径规则混在一起。
  • 结果为空:无匹配不是错误。只有业务明确要求至少一个文件时,才在收集函数外层把空结果转换成业务错误。
  • 需要可观测失败:若权限或目录读取失败必须被记录,就优先用 WalkDir,因为 Glob 会忽略访问文件系统时的普通错误。

速查:什么时候用哪种写法

需求推荐写法原因
固定一层目录中的 JSONfilepath.Glob(root/*/*.json)模式短,层级契约清晰
固定两层目录中的 JSONfilepath.Glob(root/*/*/*.json)显式表达两层目录
任意后代目录中的 JSONfilepath.WalkDir + filepath.Match不依赖不存在的递归 Glob
必须看到目录读取错误filepath.WalkDir回调可以接收并处理错误

记住一句话:filepath.Glob 解决的是“固定路径模式下有哪些匹配项”,不是“递归扫描整棵目录树”。先数清目录层级,再决定是把层级写进模式,还是把收集动作交给 WalkDir。

延伸问题

filepath.Glob 能直接用 ** 递归吗?

不能把它当作递归通配符使用。* 不跨路径分隔符;任意深度需求请使用目录遍历。

没有匹配文件时应该返回错误吗?

Glob 本身不会把无匹配当错误。是否报错取决于业务,例如“可选日报”可以返回空切片,“必须有配置”才应由业务层补充错误。

为什么 WalkDir 示例还要调用 Match?

WalkDir 负责访问目录树,Match 负责判断当前文件名是否符合规则。把职责拆开,就不会误以为 WalkDir 自带 Glob 模式。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>