PHP Directory
Directory traversal, creation and removal, and recursive scanning.
Key Points`scandir` includes `.` and `..` — skip them.
Listing
PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir);
file_put_contents($dir . "/a.txt", "a");
file_put_contents($dir . "/b.log", "b");
print_r(scandir($dir));
print_r(glob($dir . "/*.txt"));
$dh = opendir($dir);
while (($f = readdir($dh)) !== false) {
if ($f === "." || $f === "..") {
continue;
}
echo $f . "\n";
}
closedir($dh);Output
Array
(
[0] => .
[1] => ..
[2] => a.txt
[3] => b.log
)
Array
(
[0] => /var/www/demo/tmpdir/a.txt
)
a.txt
b.logscandir sorts alphabetically and includes ./... glob supports wildcards but returns full paths. opendir streams huge directories.
The Directory class
PHP
<?php
$dir = __DIR__ . "/tmpdir";
$d = dir($dir);
echo $d->path === $dir ? "路径正确" : "路径不同", "\n";
$names = [];
while (($f = $d->read()) !== false) {
if ($f[0] === ".") {
continue;
}
$names[] = $f;
}
$d->close();
sort($names);
echo implode(",", $names), "\n";Output
路径正确
a.txt,b.logdir() wraps opendir in OOP style; scandir and FilesystemIterator are more common in practice.
Recursive scanning
PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir . "/sub", 0755, true);
file_put_contents($dir . "/sub/c.txt", "c");
$files = [];
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $file) {
if ($file->isFile()) {
$files[] = $file->getFilename();
}
}
sort($files);
echo implode(",", $files), "\n";
echo count($files), "\n";Output
a.txt,b.log,c.txt
3RecursiveDirectoryIterator with RecursiveIteratorIterator is the standard, and SKIP_DOTS handles the dot entries. Safer than hand-rolled recursion, which can loop on symlinks.
Create and remove
PHP
<?php
$dir = __DIR__ . "/tmpdir";
// 递归创建
mkdir($dir . "/x/y/z", 0755, true);
echo is_dir($dir . "/x/y/z") ? "已创建" : "失败", "\n";
// 递归删除
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it as $f) {
$f->isDir() ? rmdir($f->getPathname()) : unlink($f->getPathname());
}
rmdir($dir);
echo is_dir($dir) ? "还在" : "已清空", "\n";Output
已创建
已清空Directories must be emptied before removal, hence
CHILD_FIRST. The wrong order makes rmdir fail with "not empty".Performance and safety
- **Avoid
scandiron huge dirs** —— hundreds of thousands of files eat memory - Watch symlink loops —— check
is_linkwhen recursing - Allowlist user-supplied dir names —— otherwise it is traversal
- Double-confirm deletions —— even in code
rmdir and unlink are irreversible — no trash. Test cleanup scripts on a temp dir, or move files to a trash directory first.列出目录
PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir);
file_put_contents($dir . "/a.txt", "a");
file_put_contents($dir . "/b.log", "b");
print_r(scandir($dir));
print_r(glob($dir . "/*.txt"));
$dh = opendir($dir);
while (($f = readdir($dh)) !== false) {
if ($f === "." || $f === "..") {
continue;
}
echo $f . "\n";
}
closedir($dh);Output
Array
(
[0] => .
[1] => ..
[2] => a.txt
[3] => b.log
)
Array
(
[0] => /var/www/demo/tmpdir/a.txt
)
a.txt
b.logscandir 按字母序返回,含 . 和 ..。glob 支持通配符但返回完整路径。opendir + readdir 适合超大目录(不一次性载入内存)。
Directory 类
PHP
<?php
$dir = __DIR__ . "/tmpdir";
$d = dir($dir);
echo $d->path === $dir ? "路径正确" : "路径不同", "\n";
$names = [];
while (($f = $d->read()) !== false) {
if ($f[0] === ".") {
continue;
}
$names[] = $f;
}
$d->close();
sort($names);
echo implode(",", $names), "\n";Output
路径正确
a.txt,b.logdir() 是 opendir 的面向对象包装,实际项目里用得不多——scandir 或 FilesystemIterator 更常见。
递归扫描
PHP
<?php
$dir = __DIR__ . "/tmpdir";
@mkdir($dir . "/sub", 0755, true);
file_put_contents($dir . "/sub/c.txt", "c");
$files = [];
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $file) {
if ($file->isFile()) {
$files[] = $file->getFilename();
}
}
sort($files);
echo implode(",", $files), "\n";
echo count($files), "\n";Output
a.txt,b.log,c.txt
3RecursiveDirectoryIterator + RecursiveIteratorIterator 是标准做法,SKIP_DOTS 自动跳过 . 和 ..。比手写递归安全(不会因符号链接成环而死循环)。
创建与删除
PHP
<?php
$dir = __DIR__ . "/tmpdir";
// 递归创建
mkdir($dir . "/x/y/z", 0755, true);
echo is_dir($dir . "/x/y/z") ? "已创建" : "失败", "\n";
// 递归删除
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it as $f) {
$f->isDir() ? rmdir($f->getPathname()) : unlink($f->getPathname());
}
rmdir($dir);
echo is_dir($dir) ? "还在" : "已清空", "\n";Output
已创建
已清空删目录必须先删内容再删目录本身,所以迭代器要用
CHILD_FIRST(子项优先)。顺序错了 rmdir 会报「目录非空」。性能与安全
- **超大目录别用
scandir** —— 几十万文件会吃掉大量内存 - 注意符号链接成环 —— 递归时要检查
is_link - 目录名来自用户时必须白名单 —— 否则就是路径穿越
- 删除操作要二次确认 —— 代码里也要防误删
rmdir 和 unlink 都是不可恢复的,不进回收站。写清理脚本时先在临时目录试,或者先用 rename 移到回收目录。