用 find + grep 统计 PHP 项目行数时,应结合 awk 精确匹配空行、排除 vendor/.git 等目录;PHP 脚本需用 RecursiveDirectoryIterator 和路径正则过滤,注意 BOM 处理与函数选型。

用 find + grep 快速统计项目总行数和空行数
大多数时候不需要写 PHP 脚本,Linux/macOS 命令组合更直接、更可靠。核心思路是:递归找出所有 PHP 文件,过滤掉注释和空白行后计数。
执行以下命令即可得到三类行数(总行、非空行、纯空行):
find . -name "*.php" -type f -exec cat {} ; |
awk '{lines++} /^[[:space:]]*$/ {empty++} END {print "total:", lines, "non-empty:", lines-empty, "empty:", empty}'注意点:
-
find默认会进入 .git、vendor 等目录,建议加-not -path "./vendor/*"排除 -
awk中的/^[[:space:]]*$/匹配只含空白符(空格、制表符、换行)的行,比^$更严谨 - Windows 用户可用 WSL 或 Git Bash,原生 cmd/powershell 不推荐——正则和管道行为差异大
PHP 脚本遍历目录时跳过 vendor/.git/node_modules 的关键逻辑
手写 PHP 统计脚本时,最容易出问题的是目录遍历没过滤掉第三方依赖,导致结果失真甚至 OOM。不要依赖 glob("*.php"),它不递归也不支持排除路径。
立即学习“PHP免费学习笔记(深入)”;
推荐用 RecursiveDirectoryIterator 配合白名单/黑名单判断:
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator('.', FilesystemIterator::SKIP_DOTS)
);
foreach ($it as $file) {
if (!$file->isFile() || $file->getExtension() !== 'php') continue;
$path = $file->getPathname();
// 显式跳过常见干扰目录
if (preg_match('/[\\/](vendor|node_modules|.git|cache|logs)[\\/]/', $path)) continue;
// ……后续读取并统计
}重点:
- 必须用
FilesystemIterator::SKIP_DOTS,否则.和..会被当文件处理 - 正则中同时匹配
/和是为了兼容 Windows 和 Unix 路径分隔符 - 别用
strpos($path, 'vendor')—— 万一项目名就叫myvendor就误杀了
file() vs file_get_contents() 在行统计中的实际差异
读取单个 PHP 文件内容时,选错函数会影响内存和准确性。比如统计空行,file() 按行切分后每行自带换行符,而 file_get_contents() 得自己 explode("
", ...),但后者能统一处理 CRLF/LF。
实操建议:
- 小文件(file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) 最省事,但注意:
FILE_SKIP_EMPTY_LINES会跳过空行——你没法单独统计它,所以这个 flag 不能开 - 要分别统计空行/注释行/代码行,必须保留原始行结构,推荐
file($path, FILE_IGNORE_NEW_LINES),然后逐行trim()判断 - 大项目慎用
file():它把整个文件载入内存为数组,10MB 文件可能生成百万级元素,比file_get_contents()+substr_count()更吃内存
统计“有效代码行”时绕不开的注释识别陷阱
很多人想剔除注释行来算“真正干活的代码行”,但 PHP 注释有三种形态:// 行注释、/* */ 块注释、/** */ 文档注释,且块注释可跨多行,简单正则会漏判或误杀。
真实场景下,不建议在统计脚本里手写完整 PHP 词法解析器。折中方案:
- 用
token_get_all(file_get_contents($path))—— 它能准确区分注释 token(T_COMMENT、T_DOC_COMMENT),但无法直接对应到“哪一行”,需配合行号偏移计算 - 若只要粗略过滤,可先用
php -l $file &>/dev/null确保文件语法合法,再用正则扫^s*(//|#|/*|/**)开头的行,但注意:这会把echo "/* not a comment */";这种字符串里的伪注释也干掉 - 真正需要精确度的场景(如合规审计),应调用
phpstan或php-cs-fixer的 AST 解析能力,而不是自己拼正则
最常被忽略的一点:UTF-8 BOM 头会让第一行 trim() 后仍非空,但肉眼不可见。读取前务必用 mb_substr($content, 0, 3) === "" 检查并截掉。



















