如何抢救不小心 rm 掉的数据

整理项目代码的时候,本想删除原有的 git 仓库,新建一个干净的仓库,但是不小心把rm -r ./.git按成了rm -r ./*,然后项目文件夹就……很悲剧地直接空了。万幸的是最后还是把项目成功恢复了。

NTFS 文件格式误删除恢复

今天把项目的 RL 库改成了 ETH 开源的rls_rl,但是在本地运行时受限于显卡显存大小,训练速度偏慢,所以我想着把项目文件传到服务器上进行训练。以往我一直都是用 git 来实现服务器和本地文件之间的同步的,所以这次我也照例打开命令行,下意识地想把原来的 git 仓库删除掉(项目是在 ETH 的 legged_gym 上进行修改的,所以有原项目的 git 仓库,这会影响到我后续查看 git log,所以一般我都会删掉原来的仓库,新建一个新的),就在这个过程中,发生了上述的意外——我把整个项目删掉了。

由于在 Windows 下的使用习惯,我的第一反应自然是查看回收站,结果很明显,自然是没有的。那么后续的第一件事情就是把磁盘卸载掉,卸载掉磁盘后,我开始在网上搜索如何恢复 Linux 系统下误删除的文件。

对于不同的磁盘格式,Linux 上有着不同的工具可以进行进行数据恢复。我在安装系统的时候考虑到 Windows 和 Ubuntu 双系统数据读取的便利性,所有的文件分区都是NTFS格式的,而 Ubuntu 系统可以利用 ntfs-3g 中的ntfsundelete来实现被删除文件的恢复。如果没有安装这个包的话,对于 Ubuntu 18.04 以后的版本可以使用以下命令进行安装:

1
$ sudo apt-get install ntfs-3g

然后就可以使用ntfsundelete命令来恢复文件,具体命令行参数可以参考 官方 manual

使用ntfsundelete,首先需要确定删除了哪些文件。在确保磁盘已经卸载了的情况下,运行

1
sudo ntfsundelete --scan --time 1h /dev/sda3

其中--scan参数表示列出所有被删除的文件信息,--time 1h表示筛选出一个小时内删除的文件,/dev/sda3是我删除的文件所在的分区。运行上述命令后,命令行会输出被被删除的文件的具体信息,包括 Inode,flags,age,date,time,size 和 filename,其中最关键的是 Inode。

从 Manual 来看,ntfsundelete似乎并没有提供好用的 undelete 的筛选方法,因此需要手动设置要恢复的文件的 Inode,这里可以使用常见的文本编辑器(比如 vscode)将 Inode 提取出来,并表示成ntfsundelete可以识别的格式(以逗号,隔开),然后用以下命令恢复文件:

1
sudo ntfsundelete --undelete --inodes [Inodes divided by `,`] --destination [output dir] /dev/sda3

恢复得到的文件会被保存到output dir中,这次恢复得到的文件大多是文本编辑时产生的临时文件,且数量众多(因为同一个代码文件多次编辑会产生多个临时文件),名字类似于play.py~.22,恢复的文件并不能保持原有的文件夹层次,所以进一步恢复还需要自己找出恢复文件中最新的一个版本,然后手动恢复文件夹层次。

事后:原来我早有准备

其实手动 rm 删除文件会很难恢复这件事在我刚接触 Linux 的时候就有所耳闻,在安装系统之后我也做了相关的工作,利用 bash 的 alias 把 rm 这个危险操作给它更换掉更安全的方式。知乎上有人给出了 一个 bash 的脚本 ,可以直接添加到~/.bashrc文件中,来用移动来代替rm,具体脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
alias rm=safe_rm
export TRASH_DIR={your_path}/.__trash
safe_rm () {
local d t f s
[ -z "$PS1" ] && (/bin/rm "$@"; return)
d="${TRASH_DIR}/`date +%W`"
t=`date +%F_%H-%M-%S`
[ -e "$d" ] || mkdir -p "$d" || return
for f do
[ -e "$f" ] || continue
s=`basename "$f"`
/bin/mv "$f" "$d/${t}_$s" || break
done
echo -e "[$? $t `whoami` `pwd`]$@\n" >> "$d/00rmlog.txt"
}

这个脚本会将要删除的文件移动到{your_path}/.__trash文件夹中,如果要恢复就可以从这里直接把文件复制回来(文件在移动过程中会被重命名,所以移动的时候需要改一下名字,但是这可比恢复数据容易的多了)。这个脚本就能实现递归删除,所以在删除文件的时候,要注意检查补全的名字(尤其是同一目录下有相同名字的文件夹时);另外,频繁使用这个命令来删除文件会导致磁盘空间越来越少,所以需要定时清理TRASH_DIR

实际上我这次完整数据的恢复是依赖于我的事先准备,ntfsundelete在实际运行过程中有部分被删除文件没有能够恢复,完整的项目我直接从设定好的回收站里复制出来了。所以说,有备无患啊,尤其是rm这种这么危险的操作,话说我好想服务器上还没有配过这个脚本,还能赶紧去弥补一下。