在CentOS服务器上,当你不小心用rm删除了一个正在被进程使用的文件时,别慌,文件数据其实并没有真正消失。只要持有该文件描述符的进程还在运行,你就可以通过lsof命令找到这个"已删除但仍打开"的文件,然后从/proc文件系统中把它恢复出来。具体操作就是:先用lsof -p <PID> | grep deleted找到对应的文件描述符编号,再用cp /proc/<PID>/fd/<FD> /恢复路径/文件名 把文件拷出来。整个过程不需要重启服务,不需要停机,几分钟就能搞定。
这是Linux运维中非常经典的"救命"操作,尤其在生产环境中误删日志文件、数据库文件、配置文件时特别有用。下面我会把整个流程、原理、注意事项和扩展场景全部讲透。
为什么删除文件后数据还在?很多人以为rm删除文件就是把数据从磁盘上抹掉了,其实不是。Linux的文件系统采用的是"inode引用计数"机制。一个文件在磁盘上有两层东西:一个是目录项(文件名和inode的映射),另一个是inode本身(存储文件的元数据和数据块位置)。当你执行rm命令时,系统只是删除了目录项,并且把inode的硬链接计数减1。只要还有进程打开着这个文件(即文件描述符还指向这个inode),inode的引用计数就不会归零,数据块就不会被释放。
所以你会看到一个很诡异的现象:文件已经从目录里消失了,df -h看磁盘空间也没有释放,但ls就是找不到这个文件。这就是"已删除但仍打开"的状态。理解了这个原理,你就明白为什么lsof能把它找回来。
第一步:用lsof找到被删除的文件lsof是Linux下查看打开文件的利器,全称是"List Open Files"。在CentOS上默认可能没装,需要先安装:
yum install -y lsof
安装完之后,你需要先找到是哪个进程在使用这个被删的文件。有几种方式:
方式一:如果你知道是哪个进程删的,直接查进程PID:
lsof -p 12345 | grep deleted
方式二:如果你不知道PID,但知道文件名的一部分,可以全局搜索:
lsof | grep deleted | grep "文件名关键字"
方式三:如果你知道文件大概在哪个目录下被删的,可以查该目录下相关进程:
lsof +D /var/log/ | grep deleted
执行后你会看到类似这样的输出:
nginx 12345 root 3w REG 8,1 1048576 1234567 /var/log/nginx/access.log (deleted)
这里关键信息有:进程名nginx、PID是12345、文件描述符编号是3(第4列的3w,w表示写模式)、文件路径和后面标注的(deleted)。记住这个FD编号,下一步要用。
第二步:从/proc文件系统恢复文件Linux的/proc是一个虚拟文件系统,它把每个运行中的进程信息都暴露成了文件。每个进程在/proc下都有一个以PID命名的目录,里面有个fd子目录,存放着该进程所有打开的文件描述符。
恢复命令非常简单,就是一条cp:
cp /proc/12345/fd/3 /var/log/nginx/access.log
这里12345是进程PID,3是文件描述符编号,后面是你想恢复到的路径和文件名。执行完之后,文件就完完整整地回来了,内容和删除前一模一样。
如果你想恢复的是一个大文件,比如几个G的数据库文件,cp可能比较慢。这时候可以用dd来做,效果一样:
dd if=/proc/12345/fd/3 of=/data/mysql/recovered.ibd bs=4096
bs=4096是块大小,可以根据实际情况调整。dd的好处是可以看到进度,而且对大文件更稳定。
第三步:验证恢复结果并处理后续文件恢复之后,你需要做几件事:
1. 检查文件完整性:用md5sum或sha256sum对比一下恢复前后的哈希值(如果你之前有备份的话),或者直接打开看看内容是否正常。
md5sum /var/log/nginx/access.log
2. 确认进程是否正常:恢复文件只是把数据拿回来了,但进程那边的文件描述符还是指向原来那个已删除的inode。你需要让进程重新打开文件。最简单的办法是重启该服务:
systemctl restart nginx
3. 如果不能重启服务(比如生产环境的数据库),那就需要让进程自己重新打开文件。对于nginx,可以发送USR1信号让它重新打开日志:
kill -USR1 12345
对于MySQL等数据库,通常需要执行FLUSH TABLES或者重启从库。具体操作取决于应用类型。
常见场景和实战案例场景一:误删Nginx日志文件。运维在清理日志时手滑执行了rm -rf /var/log/nginx/*.log,结果Nginx还在写日志。这时候用lsof一查就能找到,几秒钟恢复。
场景二:误删MySQL的ibdata或ibd文件。这种情况比较危险,因为数据库还在运行,直接rm可能导致数据不一致。恢复之后必须做一致性检查,比如InnoDB的innochecksum或者直接从备份恢复并做增量恢复。
场景三:误删正在被rsync传输的文件。rsync进程还在读文件,文件被删了但数据还在。恢复后rsync会继续正常工作。
场景四:误删/tmp下的临时文件。有些程序会在/tmp创建大文件做中间计算,删了之后程序可能报错但不会崩。用lsof找到fd恢复即可。
lsof的其他有用参数在实际运维中,lsof还有很多参数组合非常实用:
-i:查看网络连接相关的文件描述符,排查端口占用。
lsof -i :80
-u:按用户筛选,比如查看某个用户打开了哪些文件。
lsof -u mysql
-t:只输出PID,方便脚本处理。
lsof -t /var/log/nginx/access.log
+L1:显示链接数小于1的文件(即已删除的文件)。
lsof +L1
这些参数在排查问题时经常组合使用,建议熟练掌握。
预防措施和最佳实践虽然lsof恢复很方便,但最好的办法还是预防。以下是几条实战建议:
1. 永远不要直接rm生产环境的文件,先mv到/tmp或备份目录,确认没问题再删。
2. 重要文件开启硬链接备份,或者用rsync/快照做实时备份。CentOS可以用LVM快照:
lvcreate -s -n snap_backup -L 10G /dev/vg0/lv_data
3. 写脚本时加保护逻辑,删除前检查文件是否被打开:
if lsof /path/to/file > /dev/null 2>&1; then
echo "文件正在被使用,拒绝删除"
exit 1
fi
rm /path/to/file
4. 定期用auditd或inotifywait监控关键目录的删除操作,第一时间告警。
5. 对于数据库等关键服务,务必有完善的备份策略,不要把恢复希望全寄托在lsof上。
注意事项和坑1. 如果进程已经退出了,文件描述符就关闭了,inode引用计数归零,数据就真的没了,lsof也无能为力。所以发现误删后要第一时间操作,别拖。
2. 恢复出来的文件权限和属主可能和原来不一样,需要手动chmod/chown修正。
chown nginx:nginx /var/log/nginx/access.log chmod 644 /var/log/nginx/access.log
3. 如果文件是被truncate(截断)而不是删除,lsof显示的不是deleted而是正常路径,但文件大小变成0了。这种情况恢复方法类似,但需要从fd里拷贝时注意文件大小。
4. 在容器环境(Docker/K8s)中,/proc/<PID>/fd/ 的路径可能不太一样,需要进入容器内部操作,或者用docker cp配合。
5. CentOS 7和CentOS 8/Stream在/proc的行为上基本一致,但如果你用的是较老的内核(比如3.x),个别特性可能有差异。
总结lsof恢复已删除文件是Linux运维的必备技能,核心就三步:lsof找到deleted文件和FD编号、cp从/proc/fd/拷出来、重启或信号通知进程重新打开文件。这个操作在生产环境救过无数次火,但它只是应急手段,不能替代正规的备份策略。建议每个运维都把这套流程练熟,同时把预防措施做到位,才能真正保障系统安全。
