fnmatch仅匹配文件名而非路径或内容,正确用法是先获取文件列表再对basename调用;需注意FNM_PATHNAME禁用*匹配/、FNM_PERIOD匹配点开头文件、跨平台大小写及反斜杠转义问题。

fnmatch 只匹配文件名,不处理路径或内容
很多人一上来就用 fnmatch 去匹配完整路径(比如 "./src/*.php"),结果总返回 false。这是因为 fnmatch 的设计就是只比对单个文件名部分,不解析路径层级——它和 shell 的 ls *.php 行为一致,但不是 find . -name "*.php"。
正确做法是:先用 scandir() 或 glob() 获取目录项,再对每个条目的 basename(即 $entry)调用 fnmatch($pattern, $entry)。若硬要用路径,得手动提取文件名:fnmatch($pattern, basename($full_path))。
- 错误示例:
fnmatch("*.log", "/var/log/app/error.log")→ 总是false(因为/不在通配符规则里) - 正确写法:
fnmatch("*.log", "error.log")→true - 跨平台注意:Windows 默认不区分大小写,Linux/macOS 区分;如需统一行为,建议统一转小写:
fnmatch($pattern, strtolower($filename))
FNM_PATHNAME 标志控制 * 是否跨越目录分隔符
默认情况下,fnmatch 的 * 会匹配任意字符,包括 /。这意味着 fnmatch("a*b.txt", "a/b.txt") 居然返回 true——这通常不是你想要的,尤其做路径过滤时。
加 FNM_PATHNAME 就能禁止 * 匹配 /,让模式更贴近真实路径语义:
立即学习“PHP免费学习笔记(深入)”;
-
fnmatch("a*b.txt", "a/b.txt")→true(默认行为) -
fnmatch("a*b.txt", "a/b.txt", FNM_PATHNAME)→false(*不再吃掉/) - 常见用途:过滤当前目录下以
test_开头的 PHP 文件:fnmatch("test_*.php", $file, FNM_PATHNAME) - 注意:
FNM_PATHNAME在 Windows 上支持有限,PHP 7.4+ 已基本兼容,但仍建议测试验证
别拿 fnmatch 替代 glob,也别拿它搜文件内容
fnmatch 和 glob 定位不同:glob 是 I/O 操作,直接返回匹配的文件路径数组;fnmatch 是纯字符串比对函数,零 I/O 开销,适合在已有文件列表上快速筛一遍。
- 要列出所有
.md文件?用glob("*.md"),别写循环 +fnmatch - 已用
scandir()拿到 2000 个文件名,想挑出config_*.json?这时fnmatch才是高效选择 - 想搜索“文件里含 ‘TODO’ 的 PHP 文件”?
fnmatch完全没用——它不打开文件、不读内容。必须先用glob或RecursiveDirectoryIterator获取候选集,再用file_get_contents+str_contains或preg_match查内容 - 二进制文件风险:如果误把图片、zip 当文本读,
file_get_contents可能爆内存或触发乱码警告,务必先用mime_content_type()或简单检查前几字节
常见陷阱:点号开头文件、转义、标志组合
默认情况下,fnmatch 把 . 当普通字符。但 Unix 隐式文件(如 .gitignore)常需要显式匹配,这时就得用 FNM_PERIOD。
-
fnmatch(".*", ".gitignore")→false(默认忽略点开头文件) -
fnmatch(".*", ".gitignore", FNM_PERIOD)→true -
\在 pattern 中默认用于转义,如需字面匹配反斜杠,得用FNM_NOESCAPE,否则"a\b.txt"会被当成a[退格]b.txt - 多个 flag 要用
|组合:fnmatch("*.log", $f, FNM_PATHNAME | FNM_PERIOD) - Windows 下
fnmatch对\路径分隔符支持不稳定,建议统一用/或提前 normalize 路径
真正难的不是语法,而是判断该不该用它——它快、轻、专注,但也因此边界清晰:只碰文件名字符串,不碰磁盘、不碰内容、不碰路径结构。用错地方,效率反而更差。



















