Skip to content

误删文件抢救

01 前置理论与核心机制

1. rm 删除的底层逻辑与恢复前提

  • 指针解除而非物理覆写:在 Linux 文件系统中,执行 rm 命令本质上只是解除了目录树中的文件名硬链接(dentry),并将该文件对应的 Inode(索引节点)及数据块标记为空闲状态。操作系统并不会立即擦除磁盘物理数据块(Block)上的真实数据。
  • 首要原则:保护现场:发现误删后,严禁重启服务器(重启会产生大量系统日志覆写空闲磁盘块,且内存中的进程文件句柄会全部释放)。必须立即停止对目标分区的一切写入操作;在条件允许时,应立即将对应分区卸载(umount)或重新挂载为只读模式(mount -o remount,ro)。

2. 数据恢复的三个层级

  • 内存句柄层(100% 无损恢复):若被删文件恰好仍被某个运行中的进程打开(持有文件描述符),Linux 内核会保持其 Inode 与磁盘块不被释放。此时可直接从 /proc 虚拟文件系统中无损提取完整数据。
  • 块设备字符层(无进程占用的纯文本):在 Linux 中块设备皆为文件,可直接将整块磁盘分区(如 /dev/sdb1)视为二进制字节流,通过字符串匹配直接检索并提取未覆写的代码或配置文件。
  • 数据块扫描层(二进制文件/压缩包):依靠底层数据恢复工具(如 PhotoRec、extundelete),根据文件头尾特征码(Magic Number)在磁盘的未分配区块中进行扫描重组。

02 故障排查与恢复闭环 SOP

发生文件误删后,按恢复成功率从高到低的 3 种方案依次排查处理:

步骤一:方案 A - 进程句柄提取 (首选方案,无损恢复)

若被删除的是日志文件或正在运行的服务数据文件,通常仍有后台进程持有其文件句柄:

# 1. 查找被删文件是否仍被进程占用(状态末尾通常会带有 (deleted) 标记)
lsof | grep my_app.log
  • 观测点:若终端输出匹配记录,说明文件数据尚未释放。记录输出中第二列的 进程号 (PID)(如 5255)和第四列的 文件描述符 (FD)(如 3w 或 4r,仅取数字部分 3)。
# 2. 通过 /proc 下的文件描述符软链接直接拷贝出文件数据
cp /proc/5255/fd/3 ./my_app_recovered.log

# 3. 验证恢复的数据完整性
head -n 5 ./my_app_recovered.log

步骤二:冻结分区写入 (执行方案 B/C 的前置条件)

若无任何进程占用该文件,则必须从底层块设备中进行扫描。操作前必须阻断新数据写入以防覆写空闲块:

# 查看误删文件所在的挂载点与底层分区盘符 (如 /dev/sdb1)
df -h /data

# 停止相关写入进程后,卸载分区或将其重新挂载为只读
umount /data
# 或执行只读重挂载: mount -o remount,ro /data

步骤三:方案 B - 块设备字符检索提取 (适用于纯文本与代码)

若误删的是刚编写完成的脚本、代码或配置文件,无需安装第三方工具,可直接检索底层块设备:

# 依托文本中独有的特征字符串(如 'srv:order-api')直接扫描裸分区
# 参数说明:-m 1 (匹配1次后停止), -B 5 (提取匹配行前5行), -A 10 (提取匹配行后10行)
strings /dev/sdb1 | grep -m 1 -B 5 -A 10 'srv:order-api' > recovered_code.txt

  • 处置逻辑:绕过上层文件系统,直接扫描磁盘分区的底层字节流,定位特征字符串并将其上下文文本块截取导出。

步骤四:方案 C - 物理区块深度扫描 (适用于二进制文件/归档包)

若文件无进程占用,且为压缩包、图片或二进制文件,需使用文件恢复工具(以兼容多种文件系统的 PhotoRec 为例):

# 针对目标磁盘分区启动 PhotoRec (严禁将恢复输出目录设在故障分区自身上)
photorec /dev/sdb1

  • 分区与类型选择:在交互界面中选中目标磁盘 -> 选中对应的分区 -> 选择文件系统类型(如 ext2/ext3/ext4 或 Other)。
  • 扫描范围模式:建议选择 Free(未分配空间) 模式。该模式仅扫描已被标记为删除的空闲区块,扫描速度较快,可避免将全盘现有的正常文件一并导出。
  • 指定输出目录:选择挂载在其他正常磁盘上的目录作为恢复文件的存放路径,按 C 键开始扫描。
  • 验证点:扫描完成后,前往输出目录(如 recup_dir.1)查看。由于目录项(dentry)已被擦除,恢复出的文件会丢失原有文件名(变为 f123456.zip 格式),但文件主体内容与扩展名可被正常还原。

03 真实生产故障现象与案例库

案例 1:直接执行 rm 删除运行中的 Tomcat 日志

  • 故障现象:线上服务器磁盘空间告警达到 100%,运维人员直接执行 rm -rf /usr/local/tomcat/logs/catalina.out,发现磁盘空间并未释放,且需要审计的历史日志丢失。
  • 排查与处置:由于 Tomcat 进程仍在运行并持续持有该日志文件的写入句柄,Inode 引用计数不为 0,因此物理空间未被内核回收。通过 lsof | grep catalina.out 获取 PID 与 FD 编号后,执行 cp /proc//fd/ /backup/catalina.bak 完整恢复了历史日志。随后通过标准方式执行 : > /proc//fd/ 清空文件句柄内容,在线释放了磁盘空间。

案例 2:未备份的 Nginx 核心配置文件被误删

  • 故障现象:运维人员编写了数百行复杂的 nginx.conf 路由规则,误执行 rm 将文件删除。此时尚未执行 nginx -s reload,内存中无对应的新配置句柄。
  • 排查与处置:立即停止该节点的日志写入并将分区挂载为只读。利用配置文件中特有的域名标识 wx.api.prod.com,执行 strings /dev/vda1 | grep -B 30 -A 300 'wx.api.prod.com',直接从底层磁盘数据块中将该纯文本配置段落完整提取并还原。

案例 3:业务归档压缩包被定时清理脚本误删

  • 故障现象:清理过期文件的定时脚本因正则表达式编写有误,将业务方前一日上传的一批 .zip 合同归档包误删。
  • 排查与处置:二进制归档文件无法通过 strings 恢复。立即将对应的存储分区执行 umount 卸载,使用 photorec 在 Free 模式下仅勾选 .zip 文件特征头进行定向扫描。最终从空闲数据块中完整恢复出对应的归档压缩包,解压校验文件内部结构完好。

04 生产排障速查表(误删恢复分级)

误删场景排查/恢复层级执行命令 / 工具恢复成功率与特点文件仍被进程占用内存句柄提取lsof + cp /proc/<PID>/fd/<FD>100% 恢复。秒级无损提取,文件内容完整纯文本/代码/配置块设备字符检索strings /dev/sdb1 | grep -B -A成功率较高。无需额外安装工具,适合提取文本片段二进制/压缩包空闲区块特征扫描photorec / extundelete视写入情况而定。依赖底层数据块未被新数据覆写基础操作防护交互式确认别名alias rm='rm -i'删除前提示确认,防止单文件误操作标准防御基线回收站机制替代部署 trash-cli 工具集将删除动作转化为移动至回收站,支持按路径一键还原

05 面试应答参考话术

面试官问:“如果在线上 Linux 服务器操作时,不小心把正在运行的数据文件或者刚写好的代码删了,有什么办法可以恢复?”

可以这样回答:遇到误删文件,首要原则是立即保护现场,严禁重启服务器,并尽可能停止目标磁盘的写入操作。只要底层的物理数据块还没有被新数据覆盖,就可以按照以下三种场景由浅入深进行恢复:第一种情况:被删文件正被某个进程占用(恢复率 100%)。

如果误删的是正在运行的日志文件或数据库文件,Linux 的机制是只要进程还持有该文件的句柄,其底层的 Inode 和数据块就不会被真正释放。 我会先执行 lsof | grep <被删文件名>,找出占用该文件的进程号(PID)和文件描述符(FD)。然后直接进入 /proc/<PID>/fd/ 目录下,使用 cp 命令将该描述符对应的映射文件拷贝出来,即可实现 100% 无损恢复。第二种情况:无进程占用,但属于纯文本文件(如配置文件或代码)。

此时我会立即将对应分区卸载(umount)或重新挂载为只读模式,防止数据块被覆写。由于 Linux 下磁盘分区也是文件,只要还记得代码或配置里某一句特有的关键字,就可以直接执行 strings /dev/sdb1 | grep -B 20 -A 50 '<关键字>'。直接绕过文件系统,从底层磁盘字节流中将匹配的上下文文本块提取出来。第三种情况:无进程占用,且属于二进制文件或压缩包。

这种场景需要借助底层数据恢复工具,例如 PhotoRec 或 extundelete。选中对应的磁盘分区,使用工具的 Free 模式专门扫描未分配的空闲区块。工具会根据文件头部的特征码(Magic Number)在底层数据块中进行检索重组,将文件实体恢复到其他安全的磁盘目录中。最后在日常运维规范上,事后恢复不如事前防御。生产环境中我通常会引入 trash-cli 回收站机制替代原生的 rm 命令,或者对关键目录配置定时快照与异地备份,从机制上规避误删风险。

06 面试高频追问与应答预案

追问 1:既然 rm 删除文件后底层数据没有立即擦除,为什么执行 df -h 查看时,磁盘使用率会立即下降?(而有时候又不下降?)

  • 回答要点:
  • 取决于该文件是否仍有活跃的进程引用计数。如果文件未被任何进程打开,执行 rm 时,内核会将 Inode 的硬链接数(i_nlink)减为 0,同时由于内存引用计数(i_count)也为 0,文件系统会立即将该 Inode 关联的物理数据块标记为空闲并归还给超级块(Superblock),因此 df -h 统计的已用空间会立即下降。
  • 如果文件正被进程(如 Nginx/Tomcat)打开占用,虽然 i_nlink 降为 0(文件名从目录树消失,du 无法再统计到),但内存中的 i_count 依然大于 0,内核不会释放底层数据块,从而出现“删除了大文件但 df -h 依然显示磁盘 100%”的现象。

追问 2:通过 PhotoRec 等底层工具恢复出来的文件,为什么原有的文件名全部丢失变成了随机数字?

  • 回答要点:
  • 因为 Linux 文件系统将文件名(存储在目录项 Dentry 中)、元数据(Inode)与实际文件内容(Data Block)是分离存储的。
  • 执行 rm 后,目录项中的文件名映射以及 Inode 中的数据块指针通常已被抹除或重置(尤其是 XFS 和新版 EXT4 文件系统)。PhotoRec 采用的是文件雕复(File Carving)技术,它不依赖文件系统的目录树元数据,而是直接遍历底层的空闲物理扇区,通过匹配已知文件格式的文件头与文件尾特征码(Magic Number,例如 ZIP 的 50 4B 03 04)将数据块截取出来。由于底层数据块中不包含文件名信息,工具只能按扫描顺序为其生成流水号文件名。

07 误删句柄检索与防误删巡检脚本 (check_deleted_files.sh)

该脚本用于自动扫描当前系统中已被执行删除(deleted)但仍驻留在内存句柄中的可恢复文件,并检查系统的防误删保护配置。

#!/usr/bin/env bash
# ==============================================================================
# 脚本名称: check_deleted_files.sh
# 功能描述: 自动扫描内存中尚未释放的已删除文件句柄,并检查防误删机制 (纯只读)
# 使用方式: 在 Linux 宿主机以 root 权限执行
# ==============================================================================

set -o pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
PLAIN='\033[0m'

echo -e "${BLUE}================================================================${PLAIN}"
echo -e "${BLUE}           Linux 误删文件句柄扫描与防御基线巡检脚本             ${PLAIN}"
echo -e "${BLUE}================================================================${PLAIN}"

# 1. 扫描当前可通过内存句柄 100% 恢复的已删除文件
echo -e "\n${YELLOW}[1/2] 扫描当前系统中已删除但仍被进程占用的文件 (可无损提取)...${PLAIN}"

if ! command -v lsof &>/dev/null; then
    echo -e "  -> ${RED}[ERROR] 当前系统未安装 lsof 工具,无法扫描进程句柄。${PLAIN}"
else
    DELETED_FILES=$(lsof +L1 2>/dev/null | awk '$NF == "(deleted)" && $5 == "REG" {print $0}' | head -n 15)

    if [ -n "$DELETED_FILES" ]; then
        echo -e "  -> ${RED}[发现目标] 以下文件已被删除,但文件描述符仍在内存中存活:${PLAIN}"
        echo -e "     ${BLUE}进程名       PID      用户     FD     大小(Bytes)  原文件路径${PLAIN}"
        echo "$DELETED_FILES" | awk '{printf "     %-12s %-8s %-8s %-6s %-12s %s\n", $1, $2, $3, $4, $7, $9}'

        echo -e "\n  -> ${GREEN}[恢复指南] 若需恢复上述文件,请提取对应的 PID 与 FD (仅保留数字部分),执行:${PLAIN}"
        echo -e "     ${YELLOW}cp /proc//fd/ /tmp/recovered_file${PLAIN}"
        echo -e "  -> ${YELLOW}[空间释放] 若上述文件为预期清理的巨大日志且占满磁盘,可执行以下命令在线清空释放空间:${PLAIN}"
        echo -e "     ${YELLOW}: > /proc//fd/${PLAIN}"
    else
        echo -e "  -> ${GREEN}[OK] 当前未检测到处于 deleted 状态且被进程占用的常规文件。${PLAIN}"
    fi
fi

# 2. 检查本地防误删保护机制
echo -e "\n${YELLOW}[2/2] 检查系统防误删保护配置...${PLAIN}"
if command -v trash-put &>/dev/null; then
    echo -e "  -> ${GREEN}[OK] 系统已安装 trash-cli 回收站组件,具备文件误删缓冲防线。${PLAIN}"
else
    echo -e "  -> ${YELLOW}[INFO] 系统未安装 trash-cli 回收站工具。${PLAIN}"
fi

if grep -q "alias rm=" /etc/bashrc /etc/profile ~/.bashrc 2>/dev/null; then
    echo -e "  -> ${GREEN}[OK] 环境变量中已配置 rm 别名保护。${PLAIN}"
else
    echo -e "  -> ${RED}[WARN] 未检测到全局 rm 保护别名,执行 rm -rf 存在直接抹除数据的风险。${PLAIN}"
    echo -e "  -> ${GREEN}[加固建议] 建议引入 trash-cli 接管删除操作,或在 /etc/profile.d/ 下配置安全拦截函数。${PLAIN}"
fi

echo -e "\n${BLUE}================================================================${PLAIN}"