rm -rf 敲下去的那一刻,硬盘上的数据其实还在
「不小心删了网站目录」「数据库被误 drop」「/var/www 整个被脚本清空了」——这类事故几乎每个站长都会遇到一次。慌是没用的,首先要建立正确认知:在 ext4 上执行 rm,删除的主要是 inode 里的指向关系,文件的数据块(data block)往往还在,只是被标记为「可复用」。也就是说,只要后续没有新数据写进那些块,就有很大概率能把文件捞回来。
但反过来说,任何一次新的写入都可能永久覆盖你刚删掉的数据。所以从删错到开始恢复之间的每一个动作,都决定了成功率。这篇按「事故现场处置 → 文件系统级恢复 → 分区/引导级恢复 → 事后加固」的顺序,把 extundelete、testdisk、photorec 这套工具的实际用法和常见坑讲一遍。
第一优先级:立刻做这四件事,而不是急着装工具
绝大多数恢复失败,不是因为工具不行,而是因为「在恢复之前就已经把现场破坏了」。删除后请严格按顺序执行:
1. 立刻停止对该分区的写入。如果你删的是 /var/www,而 MySQL 还在跑、Nginx 还在写日志、系统还在写 /var/log,那你每等一秒,成功恢复的概率都在下降。最快的止损方式是把服务全停掉,甚至可以把整个系统 remount,ro:
# 停止所有可能写盘的业务
systemctl stop nginx mysql php8.2-fpm
systemctl stop cron
# 如果日志还在写,临时停掉 rsyslog / journald
systemctl stop rsyslog
journalctl --rotate && journalctl --vacuum-time=1s # 慎用,会清日志
# 最彻底:把数据分区改成只读(如果是独立分区)
mount -o remount,ro /var
# 如果数据在根分区,无法单独 remount,理想做法是重启进救援模式2. 判断文件系统类型。不同的文件系统要用不同工具。extundelete 只支持 ext3/ext4,xfs_undelete 对应 XFS,Btrfs 有自己的 snapshot 机制(如果你有快照,那恢复就是换个快照的事,根本不用 undelete 工具)。先确认:
df -T /var/www
# 或
lsblk -f
blkid3. 把工具装到另一个盘上。这条极其关键。千万不要把恢复工具装到你要恢复的那个分区上,安装过程本身就会写入数据,覆盖掉你想恢复的块。如果删的是 / 或 /var 这种系统盘上的数据,正确做法是:把这块盘挂到另一台机器上,或者用 Live USB 启动,在内存里跑系统,然后对挂载的盘做只读恢复。
# 在另一台机器上操作的最佳姿势:以只读方式挂载目标盘
mount -o ro,noload /dev/sdb1 /mnt/rescue
# noload 对 ext4 尤其重要:不重放 journal,避免挂载过程本身写盘
# 即使这样,也建议先用 dd 做全盘镜像,只在镜像上操作4. 做一份全盘镜像(如果盘不大)。这是最保险的做法:把出事的整个盘 dd 成镜像文件,之后所有恢复操作都对着镜像做,原盘保持不动。这样即使恢复搞砸了,还能重来。
# 先看盘有多大
lsblk
# 做镜像(镜像存到另一个盘!)
dd if=/dev/sdb of=/backup/sdb.img bs=4M status=progress conv=noerror,sync
# 恢复时对镜像做 loop 挂载
losetup -f --show -r /backup/sdb.img如果盘是几 TB 的 SSD,全盘镜像太慢(而且 SSD 越读越容易出问题),那就直接对原盘做只读恢复,但务必确认分区已经 remount,ro 或者根本没挂载。
extundelete:ext3/ext4 上按文件/目录恢复的利器
extundelete 的工作原理是扫描分区里残留的 inode 和目录项,重建出被删文件的路径。它最大的优势是「能按原路径恢复」,而不是给你一堆编号的乱文件。
安装:
# Debian/Ubuntu
apt install -y extundelete
# CentOS/RHEL 需要从源码编译(官方源里没有)
yum install -y e2fsprogs-devel e2fsprogs-libs
wget https://sourceforge.net/projects/extundelete/files/latest/download -O extundelete.tar.bz2
tar xjf extundelete.tar.bz2 && cd extundelete-*/
./configure && make && make install如果你用的是 XFS 或者较新的发行版,extundelete 可能装不上(它 2015 年后就基本停止维护了),这时候 ext4magic、debugfs 是替代方案,后面会讲。
先侦察,不写盘:
# 查看该分区历史上被删除过哪些文件(只读操作,安全)
extundelete --lsdel /dev/sdb1
# 输出会列出 inode、删除时间、文件大小,形如:
# Inode Size Deleted File
# 12 4096 Mon Sep 30 10:23:11 2026 /path/to/file.txt按目录恢复(推荐):
# 恢复整个 /var/www/html 目录到当前目录的 RECOVERED_FILES/
extundelete /dev/sdb1 --restore-directory /var/www/html
# 恢复单个文件
extundelete /dev/sdb1 --restore-file var/www/html/index.php
# 恢复所有已删除的文件(不推荐,会扫出大量无用 inode,耗时极长)
extundelete /dev/sdb1 --restore-all恢复出来的文件默认放在当前目录的 RECOVERED_FILES/ 里。所以一定要 cd 到另一个盘上的目录再执行,否则恢复出来的文件又写回了要恢复的分区,把还没恢复的数据覆盖了——这是 extundelete 使用中最经典的错误。
extundelete 的三个现实限制:
· 只能恢复 ext3/ext4,不认 XFS/Btrfs/ZFS。
· 目录项被覆盖后恢复不出来。ext4 的目录项里,删除文件时会把前一个目录项的 inode 和 rec_len 连起来,如果这个位置后来被新文件占用,原文件名就彻底丢了。这时只能靠 inode 号恢复(--restore-inode),但文件名未知。
· 对延迟分配(delalloc)和 ext4 的新特性支持不完整,恢复出来的文件可能大小正确但内容是零字节,或者大小对不上。恢复后一定要逐个校验(比如 file 命令看类型、图片能不能打开)。
debugfs:不用装任何东西,系统自带的分区手术刀
如果你的系统上装不了 extundelete(比如 XFS 或者没有网络),debugfs 是 e2fsprogs 自带的一个交互式工具,能直接操作 ext4 的元数据。它默认以只读模式打开,非常安全。
# 交互式进入
debugfs /dev/sdb1
# 在提示符下:
debugfs: lsdel
# 显示已删除的 inode 列表
debugfs: stat <inode号>
# 查看某个 inode 的详细信息(大小、块指针、是否还完整)
debugfs: dump <inode号> /backup/recovered_file
# 把某个 inode 的内容导出到指定路径
debugfs: quit用 stat 看 inode 时要重点看 Links: 0(链接数为 0 说明已被删除)和 Size。如果 Size 还在但块指针被清零了,那数据块已经回收,救不回来了;如果块指针还在,dump 出来的文件大概率是完整的。
debugfs 的一个典型用法是恢复被误删的单个关键文件(比如 wp-config.php 或者某个数据库 dump):先 lsdel 找到 inode,用 stat 确认块指针完好,再 dump 出来。整个过程不需要安装任何第三方工具,这也是它在救援模式下最大的价值——救援系统里通常没有网,装不了 extundelete。
testdisk + photorec:当分区表和文件系统都坏了的时候
如果问题不只是「删了文件」,而是「分区表损坏、分区丢失、磁盘变成 RAW」,那 extundelete 就无能为力了,得上 testdisk。
testdisk 恢复丢失分区:
apt install -y testdisk
testdisk /dev/sdb交互流程是:选 [Proceed] → 选分区表类型(一般 Intel,GPT 盘选 EFI GPT)→ [Analyse] → [Quick Search]。它会扫描出丢失的分区,按 P 可以预览分区里的文件,确认无误后按 Write 写回分区表。关键:在 Write 之前,用 P 预览文件列表,能看到文件名和目录结构,说明这个分区识别正确。
photorec:按文件签名恢复(不依赖任何文件系统元数据)。
photorec /dev/sdbphotorec 不看 inode、不看目录项,而是直接扫描磁盘二进制,按文件头尾签名(signature)还原文件。它支持几百种格式:jpg、png、mp4、pdf、zip、php、sql 等等。代价是:
· 所有文件都变成无意义的编号名(f0001234.jpg),目录结构完全丢失;
· 碎片化的文件会损坏(一个文件的数据块不连续时,photorec 拼不回来);
· 速度慢,几百 GB 的盘要跑几个小时;
· 会恢复出大量噪音(浏览器缓存里的缩略图、系统残留图片都被当成 jpg 捞出来)。
所以 photorec 是「最后手段」:当你连文件系统都识别不了,或者 extundelete/debugfs 都无能为力时,才用它。另外,photorec 恢复出来的文件需要一个单独的目录存放(配置界面里会让你选),务必选另一块盘。
一个实战组合:如果只是「误删文件」,先 extundelete --restore-directory;如果目录项被覆盖、恢复不出原名,就用 debugfs lsdel 找 inode 逐个 dump;如果连分区都找不到了,testdisk 恢复分区表;如果连文件系统都被格式化过,photorec 按签名抢救。这条判断链能覆盖绝大多数场景。
数据库被误 drop/误删表怎么办
数据库的情况不同,因为 InnoDB 的数据是按表空间文件组织的(.ibd),直接从文件系统恢复 .ibd 文件往往不能直接用(页头里带着事务 ID 和 LSN,直接拷回去 MySQL 会认为损坏)。正确的路径按优先级:
1. 如果有 binlog,用 binlog 做时间点恢复,这是最靠谱的,能精确恢复到误操作的前一秒。前提是 binlog 开着(log_bin = ON)且没被清理:
# 找到误操作前的位置
mysqlbinlog --base64-output=decode-rows -v /var/log/mysql/binlog.000042 | less
# 找到 DROP TABLE 那个 event 的 end_log_pos
# 用 mysqlbinlog 截取到那个位点之前
mysqlbinlog --stop-position=1234567 /var/log/mysql/binlog.000042 | mysql -uroot -p2. 如果有 mysqldump 备份,恢复到最近一次备份,再用 binlog 补上增量。这也是为什么 mysqldump 要配合 binlog 使用。
3. 如果两个都没有,尝试从 .ibd 文件恢复:先在另一个 MySQL 实例上建同名空表(表结构必须完全一致),ALTER TABLE ... DISCARD TABLESPACE,把恢复出来的 .ibd 拷进去并修好权限,再 IMPORT TABLESPACE。这个方法成功率不高,且在 innodb_file_per_table 关闭(所有表都在 ibdata1 里)的情况下基本无解。可以用 undrop-for-innodb 这个工具从 ibdata1 里捞 InnoDB 页,但操作相当复杂,属于专业数据恢复的范畴。
结论很直白:数据库的恢复,靠的是事前有 binlog 和备份,而不是事后工具。文件系统级的 undelete 对数据库基本无效。
恢复完不算完:把「防手滑」做进流程里
能恢复一次是运气,能不再出事才是能力。四条性价比最高的加固:
1. 给 rm 上保险。在 ~/.bashrc 里加一个别名,把删除变成移到回收站:
mkdir -p /tmp/trash
alias rm='mv -t /tmp/trash --backup=numbered'
# 更稳的做法:写一个脚本,删除前打印完整路径并要求确认注意 alias 只对交互式 shell 生效,脚本里的 rm 不受影响——这既是优点(脚本行为不变)也是缺点(自动脚本仍然会直接删)。脚本里更该用的是 set -u 加变量检查:
#!/bin/bash
set -euo pipefail
TARGET="${1:?必须指定目标目录}"
[ -z "$TARGET" ] && exit 1
[ "$TARGET" = "/" ] && { echo "拒绝删除根目录"; exit 1; }
echo "即将删除: $TARGET"
read -rp "确认? (yes/no) " ans
[ "$ans" = "yes" ] || exit 1
rm -rf -- "$TARGET"-u 让未定义变量直接报错退出,这是防 rm -rf $DIR/ 在 DIR 为空时变成 rm -rf / 的关键。-- 则防止路径以 - 开头被当成参数。
2. 关键目录加不可变位。chattr +i 能让文件连 root 都删不掉(除非先 -i):
chattr +i /var/www/html/wp-config.php /etc/nginx/nginx.conf
lsattr /var/www/html/wp-config.php代价是很多程序需要写这个文件时(比如 WordPress 更新、证书续期改配置)会失败,所以要选的准——选那些「一次配好、平时不改」的文件。
3. 用快照代替祈祷。如果你用 LVM 或者 Btrfs/ZFS,删除前一键快照是最强的保险。btrfs subvolume snapshot 是秒级的,快照里的文件即使被删了也还在。日常最实用的组合是「btrfs 自动快照(比如每小时一份,保留 24 份)」+ 改配置前手动打个快照。有快照的情况下,误删的恢复流程就一行命令,完全不需要 undelete 工具。
4. 备份要做「恢复演练」而不是「备份成功」。很多人备份脚本跑了半年,日志一片绿色,真出事时才发现备份文件是空的、或者恢复出来是乱码。定期(比如每季度)在一个独立环境里真的恢复一次,这是唯一能证明备份有效的方法。
小结
数据恢复这件事,成功率在删除那一刻就基本决定了。三条主线:
第一,删除后的第一反应是「停止写入」,而不是「装工具」。停服务、必要时 remount,ro、把工具装到别的盘、有条件先 dd 全盘镜像。任何多余的写盘都在销毁证据。
第二,按「误删文件 → 分区丢失 → 文件系统损坏」的层级选工具。误删用 extundelete(能按原路径恢复)或 debugfs(无需安装、救援模式可用);分区表坏了用 testdisk;文件系统识别不了用 photorec(按签名恢复,丢目录结构)。数据库则完全走另一条路——靠 binlog 时间点恢复。
第三,工具只是补救,流程才是防线。给危险命令加变量检查和确认、关键文件加 chattr +i、用快照覆盖「改配置前」这个高风险时刻、定期做恢复演练。这四件事的成本远低于一次数据事故。
顺带补一句经验之谈:恢复请求发起得越早越好,但要动手时越慢越好。急着敲命令、把工具装到受害分区上、恢复文件又写回原盘,是失败案例里出现频率最高的三个动作。深呼吸,先停写,再动手。