php opendir() 默认不跳过隐藏文件,实际行为取决于系统 readdir();常见“读不到”是因代码主动过滤,正确做法是先读全量再按业务规则过滤,而非简单判断首字符是否为点。

PHP opendir() 默认不读隐藏文件,得手动处理
PHP 的 opendir() 本身不跳过隐藏文件(即以 . 开头的文件),但它底层依赖系统 readdir() 行为 —— 在 Linux/macOS 上,readdir() 会返回 . 和 ..,也会返回其他隐藏项(如 .env、.gitignore);Windows 则基本不生成“隐藏文件”概念,但若文件被标记为 SYSTEM/HIDDEN 属性,scandir() 可能漏掉,opendir() + readdir() 却可能读到(取决于 PHP 版本和 SAPI)。所以「读不到」往往不是函数限制,而是你代码里主动过滤了。
常见错误现象:scandir($dir) 返回结果里没有 .htaccess,但用 shell ls -a 能看到 —— 很大概率是你写了类似 array_filter($files, fn($f) => $f[0] !== '.') 这种粗暴过滤。
- 不要用字符串首字符判断是否隐藏:有些合法文件名就是
.123或..config,且.和..是特殊目录项,必须单独处理,不能一棍子打死 - 真正安全的做法是:先用
readdir()拿全量,再用is_file()/is_dir()+ 显式白名单或业务规则过滤 - 如果目标只是「读取所有非 . 和 .. 的条目」,用
array_diff(scandir($dir), ['.', '..'])更稳,它不依赖首字符
用 glob() 读隐藏文件要加 GLOB_BRACE 和显式模式
glob() 默认忽略以 . 开头的文件,这是它的硬编码行为(源于 libc glob 的 GLOB_PERIOD 默认未启用),不是 bug,是设计如此。想让它吐出 .env,必须主动打开开关并写明模式。
使用场景:快速枚举配置类隐藏文件,比如加载项目根目录下的 .env、.php-version 等,又不想写完整循环逻辑。
立即学习“PHP免费学习笔记(深入)”;
- 正确写法:
glob('.*', GLOB_BRACE | GLOB_NOSORT)—— 注意GLOB_BRACE是必须的,否则.*会被当字面量,不触发通配 - 但这个会包含
.和..,记得用array_diff()剔除:array_diff(glob('.*', GLOB_BRACE), ['.', '..']) - 性能影响:开启
GLOB_BRACE会让 glob 多一次解析,但对小目录几乎无感;不过glob('.*')在大目录下比scandir()慢,因为它是按模式匹配而非直接读目录项
DirectoryIterator 默认跳过 . 和 ..,但隐藏文件照常出现
DirectoryIterator 是面向对象方式遍历目录,它内部自动跳过 . 和 ..(无需手动判断),但对其他隐藏文件(如 .git、.DS_Store)完全不设防 —— 它们会正常出现在迭代中。
容易踩的坑:以为用了 DirectoryIterator 就“安全”了,结果线上误删了 .env,只因没做业务层过滤。
- 检查是否为隐藏文件,别用
$file->getFilename()[0] === '.',改用正则:preg_match('/^\./', $file->getFilename())(更准,避免空字符串或 UTF-8 首字节问题) - 如果需要跨平台兼容(尤其 Windows),建议结合
FileInfo判断属性:$file->isDot()只认./..,而隐藏属性需用exec('attrib ' . escapeshellarg($file->getPathname()))(不推荐)或放弃 —— PHP 本身不提供跨平台隐藏属性 API - 注意
DirectoryIterator实例化时若目录不存在或权限不足,会直接抛UnexpectedValueException,必须 try/catch,不能只靠is_dir()预检
Apache/Nginx 的目录遍历防护和 PHP 无关
用户常混淆:为什么浏览器访问 /uploads/ 看不到 .htaccess,但 PHP 脚本却能读到?因为 Web 服务器(Apache/Nginx)默认禁止 HTTP 层列出或下载以 . 开头的文件,这是服务器配置层面的规则,和 PHP 的文件系统函数完全隔离。
典型错误认知:把 open_basedir 或 disable_functions 当成隐藏文件过滤器 —— 它们管的是「能不能读」,不是「要不要列出来」。
- Apache 中
.htaccess生效靠的是AccessFileName .htaccess和AllowOverride,它阻止的是外部请求,不影响fopen('.htaccess', 'r') - Nginx 默认用
location ~ /\. { deny all; }拦截,但 PHP-FPM 进程仍可读取这些文件 —— 这正是 Laravel 把.env放在 webroot 外的原因,而非指望 Nginx “藏住”它 - 真正该做的:敏感隐藏文件(如
.env)绝不放在 Web 可访问路径下;必须放时,靠 Web 服务器配置 deny,再加 PHP 层校验(如if (str_starts_with($path, '.')) die('Forbidden');)双保险
隐藏文件不是黑盒,PHP 能读到,说明它就在文件系统里明摆着。关键不在“怎么藏”,而在“谁该看”和“看了之后怎么用”。











