刪除非空目錄需遞歸清空內容再 rmdir。本文提供生產級刪除函數,處理符號連結不誤刪目標、錯誤捕獲、可選預覽模式。
<?php
/**
* 遞歸刪除目錄內容並刪除目錄本身
* @param string $dirPath 目標目錄絕對路徑
* @param bool $dryRun true=預覽模式(僅列出將刪除項), false=實際刪除
* @return array ['deleted'=>[], 'errors'=>[]]
*/
function deleteDirectoryRecursive($dirPath, $dryRun = false) {
$dirPath = rtrim(realpath($dirPath), '/');
if ($dirPath === false || !is_dir($dirPath)) {
return ['deleted' => [], 'errors' => ["Path not found or not a directory: $dirPath"]];
}
$deleted = [];
$errors = [];
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dirPath, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST // 先刪子項再刪父目錄
);
foreach ($items as $item) {
$path = $item->getPathname();
$isLink = $item->isLink();
$isDir = $item->isDir();
// 符號連結:僅刪除連結本身,不遞歸刪除目標
if ($isLink) {
if (!$dryRun) {
if (!unlink($path)) $errors[] = "Failed to unlink symlink: $path";
else $deleted[] = "symlink:$path";
} else {
$deleted[] = "symlink:$path (dry-run)";
}
continue;
}
if ($isDir) {
if (!$dryRun) {
if (!rmdir($path)) $errors[] = "Failed to rmdir: $path";
else $deleted[] = "dir:$path";
} else {
$deleted[] = "dir:$path (dry-run)";
}
} else {
if (!$dryRun) {
if (!unlink($path)) $errors[] = "Failed to unlink file: $path";
else $deleted[] = "file:$path";
} else {
$deleted[] = "file:$path (dry-run)";
}
}
}
// 最後刪除根目錄本身
if (!$dryRun) {
if (!rmdir($dirPath)) $errors[] = "Failed to rmdir root: $dirPath";
else $deleted[] = "dir:$dirPath";
} else {
$deleted[] = "dir:$dirPath (dry-run)";
}
return ['deleted' => $deleted, 'errors' => $errors];
}
// 使用範例
$result = deleteDirectoryRecursive('/var/www/temp/cache', false);
echo "Deleted: " . count($result['deleted']) . " items\n";
if ($result['errors']) {
echo "Errors:\n" . implode("\n", $result['errors']) . "\n";
}
| 特性 | 說明 |
|---|---|
| CHILD_FIRST 遍歷 | 保證先刪除檔案/子目錄,父目錄最後刪除,避免 "Directory not empty" |
| 符號連結保護 | isLink() 判斷,僅 unlink 連結檔案,不跟隨刪除目標內容 |
| 實際路徑解析 | realpath() 消除相對路徑、符號連結干擾,防止誤刪項目根目錄 |
| 預覽模式 | dryRun=true 先跑一次看清單,確認無誤再實刪 |
| 錯誤收集不中斷 | 單項失敗記錄繼續,最終彙總報告,避免半刪狀態無感知 |
function clearDirectory($dirPath) {
$dirPath = rtrim(realpath($dirPath), '/');
$items = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dirPath, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item) {
$path = $item->getPathname();
if ($item->isLink() || !$item->isDir()) unlink($path);
else rmdir($path);
}
}
// 僅限受信任路徑、已驗證輸入、關閉 shell 逃逸
exec('rm -rf ' . escapeshellarg($dirPath) . ' 2>&1', $out, $code);
強烈不建議 在 Web 請求中直接呼叫 exec,極易導致命令注入、權限越界。純 PHP 實作更安全、可移植、可測試。