運維、審計、清理場景常需「找出佔用空間超過 X MB 的文件」。本文從遞歸目錄遍歷入手,逐步構建完整的文件大小篩選工具,並實現智能單位轉換(B/KB/MB/GB/TB/PB)。
<?php
function tree($directory) {
$mydir = dir($directory);
echo "<ul>\n";
while ($file = $mydir->read()) {
if ($file === '.' || $file === '..') continue;
$path = "$directory/$file";
if (is_dir($path)) {
echo "<li><b>$file</b></li>\n";
tree($path); // 遞歸
} else {
echo "<li>$file</li>\n";
}
}
echo "</ul>\n";
$mydir->close();
}
tree("./nowamagic");
<?php
header("Content-Type: text/html; charset=utf-8");
set_time_limit(0); // 防止大目錄遍歷超時
$rootPath = __DIR__; // 掃描根目錄
$minSize = 1024 * 110; // 閾值:110 KB (字節)
$results = [];
scanLargeFiles($rootPath, $minSize, $results);
// 按大小降序排序
usort($results, fn($a, $b) => $b['size'] - $a['size']);
// 輸出結果
foreach ($results as $file) {
$relPath = str_replace($rootPath, '', $file['path']);
echo "<p>$relPath <br> {$file['size_human']}</p>\n";
}
function scanLargeFiles($path, $minSize, &$results) {
$handle = @opendir($path);
if (!$handle) return;
while (false !== ($file = readdir($handle))) {
if ($file === '.' || $file === '..') continue;
$fullPath = $path . '/' . $file;
if (is_dir($fullPath)) {
scanLargeFiles($fullPath, $minSize, $results); // 遞歸
} elseif (is_file($fullPath)) {
$size = filesize($fullPath);
if ($size > $minSize) {
$results[] = [
'path' => $fullPath,
'size' => $size,
'size_human' => formatBytes($size)
];
}
}
}
closedir($handle);
}
<?php
/**
* 位元組轉人類可讀格式 (基於 1024 進制)
* @param int $bytes 位元組數
* @param int $decimals 小數位數
* @return string 如 "1.5 MB", "1024 KB"
*/
function formatBytes($bytes, $decimals = 2) {
if ($bytes === 0) return '0 B';
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$base = 1024;
// 對數確定單位級數
$exp = (int)floor(log($bytes, $base));
$exp = min($exp, count($units) - 1); // 防止超出數組
$value = $bytes / pow($base, $exp);
return round($value, $decimals) . ' ' . $units[$exp];
}
// 測試
echo formatBytes(1023); // 1023 B
echo formatBytes(1024); // 1 KB
echo formatBytes(1536000); // 1.47 MB
echo formatBytes(1073741824); // 1 GB
<?php
// find_large_files.php - 命令行工具版
#!/usr/bin/env php
<?php
$options = getopt("d:s:e:h", [
"dir:", "size:", "exclude:", "help", "json", "parallel::"
]);
$dir = $options['d'] ?? $options['dir'] ?? '.';
$minSize = parseSize($options['s'] ?? $options['size'] ?? '100M');
$exclude = $options['e'] ?? $options['exclude'] ?? '';
$asJson = isset($options['json']);
$parallel = isset($options['parallel']);
function parseSize($str) {
$str = strtoupper(trim($str));
$unit = 1;
if (preg_match('/^(\d+(?:\.\d+)?)\s*([KMGTPE]?)B?$/i', $str, $m)) {
$val = (float)$m[1];
switch ($m[2]) {
case 'K': $unit = 1024; break;
case 'M': $unit = 1024**2; break;
case 'G': $unit = 1024**3; break;
case 'T': $unit = 1024**4; break;
case 'P': $unit = 1024**5; break;
case 'E': $unit = 1024**6; break;
}
return (int)($val * $unit);
}
return 1024 * 1024 * 100; // 默認 100MB
}
// ... 並行掃描實現省略,使用 pcntl_fork 或 pthreads ...
// 單線程版掃描邏輯同前文 scanLargeFiles()
// 輸出
if ($asJson) {
echo json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} else {
foreach ($results as $f) {
echo sprintf("%-12s %s\n", $f['size_human'], $f['path']);
}
}
# 查找當前目錄下超過 500MB 的文件,輸出 JSON
php find_large_files.php -d . -s 500M --json
# 排除 vendor 目錄,人類可讀格式
php find_large_files.php -d /var/www -s 1G -e vendor
# 並行掃描加速(需 pcntl 擴展)
php find_large_files.php -d /data --parallel
| 問題 | 原因 | 解決方案 |
|---|---|---|
| filesize() 返回 false | 文件超過 2GB(32位系統)或無權限 | 使用 stat()['size'] 或升級 64位 PHP |
| 掃描極慢 | 大量小文件、網絡掛載點、權限遍歷 | 排除目錄、使用 find 命令配合 exec() |
| 內存耗盡 | 結果集過大全部存入數組 | 流式輸出/寫入文件/生成器 yield |
| 符號鏈接循環 | 鏈接指向父目錄 | 記錄已訪問 inode、限制遞歸深度 |
對於超大規模文件系統,原生 find 性能碾壓 PHP:
# 查找 >100M 文件,按大小排序
find /path -type f -size +100M -printf "%s %p\n" | sort -rn | head -20
# 獲取目錄佔用大小
du -h --max-depth=1 /path | sort -hr
PHP 適合集成在 Web 介面、API 服務中;純運維排查首選系統工具。兩者結合,構建「Web 可視化 + 後端 find 加速」的混合方案效果最佳。