统计gcc源码C文件总大小时bash循环无法终止问题
GCC源码统计脚本统计.c文件卡死问题
问题复现
计划分类统计GCC 11.2.0的源码规模,先编写脚本统计cpp类型文件,代码如下:
# NOTE: the cpp loop finishes immediately LOC=0 BYTES=0 FILES=$(find . -name "*.cpp") for f in ${FILES}; do BYTES_TAG=$(stat --printf="%d" $f) LOC_TAG=$(cat $f | wc -l) BYTES=$((BYTES+BYTES_TAG)) LOC=$((LOC+LOC_TAG)) done echo "LOC = $LOC, SIZE = $BYTES"
统计cpp文件时脚本瞬间执行完成,但将匹配规则改为*.c统计所有C文件时,bash循环长时间无响应、无法终止。
测试环境下源码为官方解压的原始包,未做额外修改,执行全量文件计数命令时可以瞬间返回结果:
find . -type f | wc -l
核心原因
脚本本身存在多处逻辑缺陷,叠加GCC源码的文件结构特点导致了假卡死现象:
- 文件遍历写法有缺陷:直接将
find结果存入变量、用不带引号的for f in ${FILES}遍历,完全没有处理带特殊字符(空格、换行、通配符)的文件名,一旦文件名被拆分出特殊值(比如单独的-),cat命令会进入等待标准输入的状态,直接挂住整个循环。 - 逐文件调用命令的开销被量级放大:GCC源码中cpp文件仅千余个,每个文件单独调用
stat、cat、wc的进程开销感知不明显;但源码中.c文件数量达数万个,高频创建进程的开销会让脚本运行速度暴跌,看起来和卡死无异。 - 额外逻辑bug:
stat --printf="%d"获取的是文件所在设备的ID,不是文件字节数,要统计文件大小应该用%s参数;find命令未加-type f参数,会匹配到目录、符号链接等非普通文件,进一步增加无效遍历。 find . -type f | wc -l运行速度快的原因是该命令为管道流式处理,不需要把所有文件路径存到变量里,也不需要对每个文件单独启动额外进程,自然执行效率极高。
修正方案
不要用逐文件起进程的循环写法,直接用find配合批量处理命令,效率会提升两个数量级以上:
# 统计cpp文件 echo "cpp文件统计:" find . -type f -name "*.cpp" -print0 | du --files0-from=- -c -b | tail -n1 find . -type f -name "*.cpp" -print0 | xargs -0 wc -l | tail -n1 # 统计c文件 echo "c文件统计:" find . -type f -name "*.c" -print0 | du --files0-from=- -c -b | tail -n1 find . -type f -name "*.c" -print0 | xargs -0 wc -l | tail -n1
如果一定要保留循环写法,必须用兼容特殊文件名的遍历逻辑,同时修正参数错误:
LOC=0 BYTES=0 # -print0和read -d ''配合,兼容所有合法文件名 find . -type f -name "*.c" -print0 | while IFS= read -r -d '' f; do BYTES=$((BYTES + $(stat -c "%s" "$f"))) LOC=$((LOC + $(wc -l < "$f"))) done echo "LOC = $LOC, SIZE = $BYTES"
内容的提问来源于stack exchange,提问作者OrenIshShalom
相关产品推荐
相关产品推荐

