Bash脚本遍历大量文件时内存异常耗尽问题排查求助
我开发了一款Bash脚本,在遍历大量文件的循环过程中出现异常内存占用。运行一段时间后,内存被完全耗尽,系统开始交换分区,导致无法使用。通过在代码中添加监测点后,我认为问题出在
_do_cmd()函数中,以下是简化后的脚本:#!/bin/bash WORKINGDIR=$(dirname "$0") SCRIPT=$(basename $0) LOGFILE=$WORKINGDIR/test-log.txt FILELIST=$WORKINGDIR/file.list # 这里省略了部分代码... _do_cmd() { # 函数内容,处理每个文件的逻辑 } while read -r filename; do _do_cmd "$filename" done < "$FILELIST"
嗨,针对你遇到的这个内存耗尽问题,结合你锁定的_do_cmd()函数,我整理了几个常见的排查方向和实用修复建议:
1. 未关闭的文件描述符累积
Bash不会自动回收子进程或手动打开的文件描述符,如果_do_cmd()里有文件操作、管道或重定向但没正确关闭,循环几百上千次后会占用大量内存。比如:
- 用
exec 3>"somefile"打开了文件描述符,但没在函数结束时执行exec 3>&-关闭 - 管道操作(如
cmd1 | cmd2)的输出没处理完,残留的句柄会一直占用资源
排查方法:在循环里加入lsof -p $$($$是当前脚本的PID),观察每次循环后打开的文件描述符数量是否持续增长。
修复示例:确保手动打开的描述符及时关闭
_do_cmd() { exec 3>"$1.tmp" # 这里写你的业务逻辑 exec 3>&- # 显式关闭文件描述符3 }
2. 变量未重置导致的内存膨胀
如果_do_cmd()里用了全局变量,或者局部变量没在每次调用时清空,内容会持续累积,比如:
# 错误示例:全局变量output每次循环都追加内容 output="" _do_cmd() { output+=$(some_command "$1") }
排查方法:在循环前后用echo "${#output}"查看变量字符串长度,或者用declare -p output查看变量内容大小。
修复示例:使用局部变量自动回收内存
_do_cmd() { local output="" # 局部变量,函数退出后自动销毁 output=$(some_command "$1") # 处理output的逻辑... }
3. 子进程/僵尸进程残留
如果_do_cmd()里启动了后台进程但没等待它结束,或者用&启动进程后没回收,大量僵尸进程会慢慢耗尽系统资源(虽然单进程内存占比小,但架不住数量多)。
排查方法:用ps aux | grep $$查看脚本的子进程数量是否持续增加。
修复示例:等待后台进程完成后再退出函数
_do_cmd() { some_long_running_task "$1" & local task_pid=$! wait $task_pid # 等待子进程执行完毕 }
4. 未处理的大体积命令输出
如果_do_cmd()里有命令产生大量输出,却用var=$(cmd)把全部输出存到变量里,或者没把无用输出重定向走,这些内容会一直占着内存。
修复示例:丢弃无用输出,或只保留需要的内容
_do_cmd() { # 丢弃所有标准输出和错误输出 some_noisy_command "$1" >/dev/null 2>&1 # 只过滤保存关键内容 local needed_content=$(some_command "$1" | grep "核心关键字") }
快速验证技巧
你可以先把_do_cmd()替换成空函数测试:
_do_cmd() { : # 空操作,什么都不做 }
如果内存不再增长,那问题肯定在_do_cmd()的业务逻辑里;如果还是增长,就要检查循环本身或者全局变量的处理了。
内容的提问来源于stack exchange,提问作者liv913

