4

写了个Bug,误执行rm -fr /*,瞬间背后一凉!

 4 years ago
source link: https://developer.51cto.com/art/202005/615911.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

写了个Bug,误执行rm -fr /*,瞬间背后一凉!

写了个Bug,误执行rm -fr /*,瞬间背后一凉!

前几天,临近五一节,想到有 5 天假期,我就开始飘了。于是写个简单的 Bash 脚本都不上心了,写完后检都不检查,直接拖到到实体服务器跑。

前几天,临近五一节,想到有 5 天假期,我就开始飘了。于是写个简单的 Bash 脚本都不上心了,写完后检都不检查,直接拖到到实体服务器跑。

4954c0e6ea2b208802028bd18ec958b9.jpg-wh_651x-s_2239968382.jpg

图片来自 Pexels

结果一跑起来,发生不对劲,怎么一个简单脚本跑了 10 秒还没结束,于是立马 Ctrl+C 一顿操作停掉了运行中的脚本。

a1db8cea2ecefe5dfd3b57dbb7394aa8.jpg

接着,习惯性的输入了 ls,结果 what?找不到 ls 命令?

0f9fbc4db505e92a12c72d55cc089274.jpg

瞬间背后一凉,慌慌张张打开了脚本。发现问题了,我写了个巨蠢的 Bug,间接执行了 rm -fr /* ,这不意味着我删库了?

e81835a82790a98b17d3fb74d32f84e7.jpg

这台是公司的授权服务器呀,被我这么一整,公司历史的授权记录和其他重要信息不就丢了?

心里慌的一批的我,跟我的朋友们说了这件事,朋友建议我先第一时间上报给 Leader,不要把删库的事情瞒着。

于是,我就向 Leader 说了我删库事情,本以为会被痛批一顿。

结果 Leader 笑着说:“没事,你先看看重要的文件还在不在。不过你这么一整,我突然想起编译服务器半年没备份,我先备份一下我的编译服务器,防止哪天也被你们删库了。”

我:“????”

吃瓜的小伙伴,是不是觉得我要删库跑路了?哈哈哈,我没跑路,反而是恢复了回来,所以接下来说说我是如何从删库到恢复的。

初探案发现场

先来看看我写的垃圾代码,是如何引发这次的删库: 

134646e65e7323ea2c919bf3b55fe670.jpg

既然发生了 rm -fr /* 的现象,那必然 new_lic_dir 这个变量是空的。

所以导致执行 rm -fr $new_lic_dir/* 这条语句的时候,变成了 rm -fr /* 删库语句。很好,凶器找到了。

那为什么 new_lic_dir 会是空的呢?细心的小伙伴肯定察觉出来了,是因为给 new_lic_dir 变量赋值的时使用了反引号。 

3eeff241ebe5305d10c546cdf13154b7.jpg

没错,就是反引号的原因。反引号在 Linux Shell 命令行中有特殊的含义:反引号间的内容,会被 Shell 先执行。其输出被放入主命令后,主命令再被执行。

也就是说, new_lic_dir 的值是 ${lic_path}/new_license 这条命令执行的结果,问题这哪是命令啊,所以肯定返回空值给 new_lic_dir 变量。

我写的那么温柔的代码,竟然变成了穷凶极恶的删库代码。

69257d7be92cd6d585fe7e045d7a197e.jpg

这下原因是找到了,反引号应该改成双引号才对。(内心 OS:你真菜呀,那么简单的赋值命令都写错!)

哈哈哈,确实菜,都说了嘛,快五一了,是飘着写这份代码的。所以习惯性开启程序员内容的第一大武功:Crtl+C 和 Crtl+V。

把第一条赋值 lic_path=`pwd` 语句,复制粘贴了,然后只改了变量名,没注意反引号要修改成双引号,所以造成了删库的悲剧。

保留案发现场

既然发生了删库的事情,千万不要重启服务器,也不要关闭 SSH 连接的会话,而是要保留案发现场,接着查查还剩什么。

PS:这不是吹大炮嘛?ls 都没了,还怎么查?

还好这次是比较幸运,因为在执行脚本的时候,第一时间发现不对劲,立马掐断了还在运行的脚本,所以并非 Linux 所有文件都被删除了。

只要我掐的快,rm -fr /* 就干不死我。虽然 ls 被删了,但所幸发现 cd 命令还能用。

只要 cd 用的好,它也能用出的 ls 效果。很简单,只需 cd + Tab 键就会自动出现指定目录下的所有文件。

15d8948dc425284c7cfef2323a46e175.jpg-wh_600x-s_4267797185.jpg

有了 cd+Tab 键,我们就可以查看每个目录下的文件,于是就可以一步一步来确认哪些系统文件被删了。

通过一番的确认和对比后,发现主要被删除的有四个目录分别是:

  • /bin 、/boot 、/dev 这三个目录整个都被删除了。
  • /lib 目录里的动态库部分被删除。

来复习下上面这四个目录主要是存放了什么:

  • /bin 存放常用系统命令,ls、cp、rm、chmod 等常用命令都在此目录。
  • /boot 系统启动目录,保存与系统启动相关的文件,如内核文件和启动引导程序。
  • /dev 设备文件保存位置。
  • /lib 存放程序所需的动态库和静态库文件。

/boot 都被删除了,还好小林没有重启服务器,要是重启了服务器,就完犊子了,系统肯定起不来了。

cd 命令是在 /sin 目录下,/sin 还健全,所以 cd 是可以正常使用。

所幸重要的数据库信息和文件都还没删除,所以小林首要的目标是要恢复 /bin、/boot、/dev、/lib 这四个目录。

还原文件

由于 /bin 目录 和 /lib 部分动态文件被删除,常用的传递文件的方式是无法使用的,如 ftp、scp、mount 等。

我摸索了很久,竟然发现 wget 可以使用,wget 命令是在 /usr/bin 目录,所幸 /usr/bin 还健全。

于是,用了取巧的方法,先另一台正常的服务器,把 /bin 目录放到了 Web 服务器的 Web 目录,接着通过 wget 进行下载。

c78bda6f297bd01fd4ff92bbe820593a.jpg-wh_600x-s_3249267779.jpg

有戏,看到了成功的曙光。

但是新的问题就来了,我下载过来的命令文件,是没有执行权限的。

而 chmod 命令是在 /bin 目录的,它同样也被删除了,无法使用它来给予文件权限。

还在,在网上搜到了一个伟大命令 perl,可以通过它来给予文件权限:



  1. perl -e "chmod 777, 'ls'" 

真是个神奇的命令。好了,这下赋值权限问题也解决了,成功在望了。

wget 是无法直接把 /bin 目录下载下来的,只能下载一个文件。

但是我不可能一个一个去下载来进行恢复,这得要何年何月才能完成......

于是我就想到了一个方法:

  • 先通过 wget 的方式下载 tar 命令,并通过 perl 给予 tar 命令权限。
  • 接着把另一台服务器把 /bin 目录打包成压缩文件,然后通过 wget 下载 bin 目录的压缩包文件。
  • 最后通过 tar 命令把 bin 压缩包解压出来。

/bin 就这样恢复回来啦,剩余的其他目录 也是通过同样的操作恢复了回来。

我的笑容渐渐恢复了回来,哈哈哈哈哈哈哈哈哈哈哈哈! 

b82056c233978cf93127a8d249e6ddc3.jpg

遇到 rm -fr /* 删库事件发生,一定要沉住气,稳住心态!

本次删库事件,之所以能幸运的恢复回来,有非常关键两点:

  • 发现脚本执行不正常,果断立马的掐断它,没有造成重要的数据库信息被删除,如果掐断的时候再晚一点,可能就真没了。
  • 发现常用命令无法使用的时候,没有重启服务器,不然服务器就起不来了,也没有关闭 SSH 会话,不然无法在重新连接 SSH 会话了,也就无法进行操作了。

如果以上两点都没做好,服务器恢复的难度就加大了很多,更严重的是五一节就没的过了。  

fcf7fd28e1e21624ced5fc03be27e231.jpg

预防误执行 rm -fr /*

既然 rm -fr /* 是残忍的凶器,那么预防它是很有必要的,接下来跟大家讨论讨论预防它的几种方案。

方案一:rm -rf 删除目录时要判断目录 



  1. #!/bin/bash 
  2. work_path=`pwd` 
  3. #如果目录不为空,才执行删除操作 
  4. if [ ${work_path} != "" ];then 
  5.     rm -fr ${work_path}/* 

在执行删除目录操作前,先判断要删除的目录是否为空,不为空才执行删除操作。

方案二:Shell 脚本指定 set -u

执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它。



  1. #!/bin/bash 
  2. echo $a 
  3. echo hello 

上面代码中,$a 是一个不存在的变量,执行结果如下。



  1. $ bash test.sh 
  2. hello 

可以发现,echo $a 输出了一个空行,Bash 忽略了不存在的 $a,然后继续执行 echo hello。

最好是遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。

set -u 就用来改变这种行为,在脚本加上它,遇到不存在的变量就会报错,并停止执行。



  1. #!/bin/bash 
  2. set -u 
  3. rm -fr $a/* 
  4. echo hello 

运行结果如下:



  1. $ bash test.sh 
  2. test.sh: line 4: a: unbound variable 

可以看到,因为 a 是未定义变量,脚本报错了,并且不再执行后面的语句。

方案三:safe-rm 替换 rm

safe-rm 是一个开源软件工具,这名字听起来就很安全嘛,所以它是用来替代不太安全的 rm。

它可以在 /etc/safe-rm.conf 中配置路径黑名单,定义哪些不能被 safe-rm 删除。

我们可以将 safe-rm 更名为 rm,假设定义了 /etc/ 无能被删除,那么删除 /etc 时就会报错:



  1. $ rm -rf /etc/ 
  2. safe-rm: skipping /etc/ 

方案四:建立回收站机制

Windows 是有回收站的,即使误删了,也可以在回收站恢复。所以,我们也可以在 Linux 实现回收站的机制。

实现思路:

  • 删除文件时,它并不真正执行删除操作,而是将文件移动到一个特定目录,可以设置定时清除回收站,或者在回收站里面的文件大小达到一定容量时(或者用时间做判断)执行删除操作以腾出空间。
  • 可以写个 Shell 脚本替换 rm 命令,或者在需要删除文件的时候使用 mv 命令将文件移动到回收站。

① 创建回收站目录



  1. mkdir /home/.trash 

② 编写 remove.sh 脚本,内容如下:

6f05f6936ba553d9a6018c8c86fb53b8.jpg

③ 修改 ~/.bashrc, 用我们自建的 remove.sh 替代 rm 命令:



  1. alias rm="sh /home/remove.sh" 

④ 设置 crontab,定期清空垃圾箱,如每天 0 点清空垃圾箱:



  1. 0 0 * * * rm -rf /home/.trash/* 

⑤ 最后,执行以下命令,使之生效:



  1. source ~/.bashrc  

方案五:根文件挂载成只读

在 /etc/fstab 文件,把 / 文件系统挂载成只读的方式。

f6f6d9e98292febd73d35d6637e41bfe.jpg-wh_600x-s_3625853107.jpg

其中 remount,ro,就表示只读的方式挂载。

只读的方式挂载后,进行删除操作是无法成功的:

77f0be5da961a59e62405f44609d5989.jpg

事后反思

涉及到 rm -fr 命令的代码,要留个心眼,要反复检查,要做好预防误执行 rm -fr /*,并在测试机验证完后,再拖到实体机上跑,千万不可大意。

就算的发生了 rm -fr /*,要第一时间停掉它,并且要做到三不要:

  • 不要慌,不要心跳爆炸(稳住稳住)
  • 不要隐瞒删库事件(不丢人)
  • 不要重启服务器或断开 ssh 会话(保留现场)

只要立马掐断 rm -fr /* ,它是干不死我们的。利用当下环境剩有的命令,冷静分析,是有机会恢复的。

ec8aefe30476e3ec59ea5085fcbfc171.jpg-wh_600x-s_1160900957.jpg

我现在是一个删过库没跑路的男人了,Goodbye, 我们下次见。

作者:小林

编辑:陶家龙

出处:转载自微信公众号小林 coding(ID:CodingLin)

7051922ad82b81ea4b70ba485d394746.gif

【编辑推荐】

【责任编辑:武晓燕 TEL:(010)68476606】

点赞 13

关注“51CTO技术栈”微信公众号获取更多精彩内容


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK