最近看见好几起因为rm -rf引发的惨案,痛定思痛,随决定改变自家服务器现状,解决目前rm -rf存在的隐患。
首先就是调研了,了解过市面上已有的保护rm -rf的方法和插件,各有优缺点,在根据自己实际需求的基础上,最终选择了模拟回收站的功能(基于网友已有成果的基础上)。接下来给大家分析一下我的调研成果(参考:https://github.com/alanzchen/rm-protection):
Methods
Can be used as "rm"?
Protect Specific Files
Flexibility
Additional Files
trash-cli
Yes
Somehow
High
Centralised Config
rm -i
Yes
No or Somehow
High
safe-rm
Yes
Yes
Low
Centralised Config
rm-protection
Yes
Yes
High
One for each
简单的说就是,trash_cli模拟的是回收站功能,就功能来说还算不错,但因为是别人写好的插件,和自己的需求有一定的差异,更改插件比较麻烦;rm -i就是每次使用rm的时候,添加-i参数,提示用户是否删除,用这种方法删除文件麻烦不说,还容易忘记;safe-rm通过配置文件保护特定的文件,但麻烦的就是当需要删除保护文件的时候,每次都要更改配置文件;最后一个就是rm-protection了,这个是最贴近需求的——通过命令对特定文件进行保护,在当前目录下生成隐藏的配置文件,在配置文件中保存了一组问答,当删除保护文件时产生询问,回答正确问题才能删除文件。
rm-protection我亲自测试过,效果挺不错的,但是存在以下几点问题,导致我放弃了。1、对每一个特定文件都要产生一个配置文件,虽然配置文件是隐藏的,但对于完美主义来说,看着还是挺难受的;2、保护文件夹后不保护文件夹中的内容,也就是说保护特定文件夹后,依然可以对文件夹中的内容进行删除操作,这与有的需求产生了差异;3、因为这是别人的插件,后期难免有新需求的情况下,更改起来就比较难了。
而我目前的需求来说有以下几点——1、当使用rm删除文件出错后,能够迅速恢复过来,rm -rf的惨剧;2、功能适用于普通用户rm,root用户rm,以及普通用户sudo rm(涉及到环境变量的问题,比较麻烦,我也是具体实现时才发现这个问题的,下面会详细介绍),并且兼容新增用户;3、不影响其他人的正常使用,因为服务器不仅是我一个人使用,其他人也会登录操作,所以rm命令不能更改。
综上所述,最终我决定自己在Linux上模拟一个回收站的功能,并且让功能满足我们的需求。回收站的原理很简单(参考:http://blog.csdn.net/u014057054/article/details/52126494,这也是个悲惨的故事),在/home目录下新建一个.trash的隐藏文件夹作为回收站,以后每当rm删除文件或文件夹时,就将它移动到.trash文件夹(也就是回收站)中,回收站中的文件能够恢复到原来目录、回收站中的文件能够单独删除、回收站中的文件能够全部清除,并且通过crontab定时任务每周清空回收站。
接下来就是回收站的实现了,实现代码trash.sh存放在/etc目录下,代码如下:
# 回收站存放路径
trash_path='/home/.trash'
# 文件夹不存在就新建文件夹,存在也不报错
mkdir -p $trash_path
# 回收站命令格式
# 删除命令trash,恢复命令undelfile
if [ $# == 2 ]; then
if [ $1 == 'undelfile' ] || [ $1 == 'trash' ] || [ $1 == 'delfile' ]; then
value1=$1
value2=$2
else
echo 'The command is wrong!!!'
exit
fi
# 打印回收站文件列表命令ll/ls,清空回收站命令cleartrash/cleartrash_direct
elif [ $# == 1 ]; then
if [ $1 == 'll' ] || [ $1 == 'ls' ] || [ $1 == 'cleartrash' ] || [ $1 == 'cleartrash_direct' ]; then
value1=$1
else
echo 'The command is wrong!!!'
exit
fi
# 删除命令trash(支持rm -rf参数)
elif [ $# == 3 ] && [ $2 == '-rf' ]; then
if [ $1 == 'trash' ]; then
value1=$1
value2=$3
else
echo 'The command is wrong!!!'
exit
fi
else
echo 'The command is wrong!!!'
exit
fi
# 删除文件
if [ $value1 == 'trash' ]; then
input=$value2
if [ ${input:0:1} == "/" ]; then
if [ ${input: -1} == "/" ]; then
input=${input%?}
fi
filename=${input//\//\\}
mv -v $value2 $trash_path/$filename
else
path=$(/bin/pwd)
path=${path//\//\\}
filename=$path\\$value2
mv -v $value2 $trash_path/$filename
fi
# 恢复文件
elif [ $value1 == 'undelfile' ]; then
if [ ${value2:0:1} == '/' ]; then
path=$value2
filename=${value2//\//\\}
elif [ ${value2:0:1} == '\' ]; then
path=${value2//\\/\/}
filename=$value2
fi
mv -iv $trash_path/$filename $path
# 删除回收站中指定文件
elif [ $value1 == 'delfile' ]; then
if [ ${value2:0:1} == '/' ]; then
filename=${value2//\//\\}
elif [ ${value2:0:1} == '\' ]; then
filename=$value2
fi
/usr/bin/rm -rfv $trash_path/$filename
# 打印回收站文件列表
elif [ $value1 == 'ls' ]; then
ls $trash_path
# 打印回收站文件列表
elif [ $value1 == 'll' ]; then
ls -l --color=auto $trash_path
# 清空回收站文件,有询问
elif [ $value1 == 'cleartrash' ]; then
read -p "clear sure?[n]" confirm
[ $confirm == 'y' ] || [ $confirm == 'Y' ] && /usr/bin/rm -rfv $trash_path/*
# 直接清空回收站文件,无询问
elif [ $value1 == 'cleartrash_direct' ]; then
/usr/bin/rm -rfv $trash_path/*
fi
然后就对回收站的命令使用别名,将rm命令绑定回收站功能,不影响其他用户的使用。为了使回收站功能对所有用户均有效,别名设置在/etc/bashrc文件中,并且加载资源source /etc/bashrc,操作同设置环境变量一样。
# add my own trash
alias sudo='sudo '
alias rm='sh /etc/trash.sh trash'
alias r='sh /etc/trash.sh trash'
alias rl='sh /etc/trash.sh ls'
alias rls='sh /etc/trash.sh ls'
alias rll='sh /etc/trash.sh ll'
alias ru='sh /etc/trash.sh undelfile'
alias rd='sh /etc/trash.sh delfile'
alias cleartrash='sh /etc/trash.sh cleartrash'
alias cleartrash_direct='sh /etc/trash.sh cleartrash_direct'
其中需要特别说明的就是:
alias sudo='sudo '
当普通用户使用sudo执行命令的时候,Linux为了安全考虑,使用的既不是当前用户的环境变量,也不是root用户的环境变量,而是使用sudoers中设置的环境变量,这对于别名也同样适用。但文件就是/etc/sudoers中能够设置环境变量,却不能够设置别名alias。
为了解决这个问题,我也是寻找了需要方案,最终找到了下面的博客:
http://blog.csdn.net/killerpro/article/details/53219660
http://askubuntu.com/questions/22037/aliases-not-available-when-using-sudo
使用别名对sudo命令后添加一个空格,这时再使用sudo执行命令的时候,就会使用当前用户的环境变量与别名设置,而不是sudoers中设置的环境变量。原理如下:
Add the following line to your ~/.bashrc:
alias sudo='sudo '
From the bash manual:
Aliases allow a string to be substituted for a word when it is used as the first word of a simple command. The shell maintains a list of aliases that may be set and unset with the alias and unalias builtin commands.
The first word of each simple command, if unquoted, is checked to see if it has an alias. If so, that word is replaced by the text of the alias. The characters ‘/’, ‘$’, ‘`’, ‘=’ and any of the shell metacharacters or quoting characters listed above may not appear in an alias name. The replacement text may contain any valid shell input, including shell metacharacters. The first word of the replacement text is tested for aliases, but a word that is identical to an alias being expanded is not expanded a second time. This means that one may alias ls to "ls -F", for instance, and Bash does not try to recursively expand the replacement text. If the last character of the alias value is a space or tab character, then the next command word following the alias is also checked for alias expansion.
(Emphasis mine).
Bash only checks the first word of a command for an alias, any words after that are not checked. That means in a command like sudo ll, only the first word (sudo) is checked by bash for an alias, ll is ignored. We can tell bash to check the next word after the alias (i.e sudo) by adding a space to the end of the alias value.
大概意思就是添加空格的效果,就是alias将sudo与sudo后面的命令看作是同一个条命令,在这样的情况下,这个结合起来的命令使用的就是当前用户的环境变量与别名,而不是sudoers中设置的环境变量与别名。(不知道这样解释各位明不明白?)
在这里回收站的基本功能就差不多实现完全了,rm/r/rm -rf删除文件或文件夹到回收站/etc/.trash中,rl/rls/rll打印回收站的文件列表,ru恢复回收站中的指定文件,cleartrash/cleartrash_direct清空回收站中的所有文件,用法如下:
$ cd /home
$ rm aa/
‘aa/’ -> ‘/home/.trash/\\home\\aa/’
$ rm -rf bb/
‘bb/’ -> ‘/home/.trash/\\home\\bb/’
$ rl
\home\aa \home\bb \home\user\tangyy\bb
$ rls
\home\aa \home\bb \home\user\tangyy\bb
$ rll
total 8
drwxr-xr-x 2 root 0 4096 Mar 21 16:02 \home\aa
drwxr-xr-x 2 root 0 4096 Mar 21 16:02 \home\bb
$ ru \\home\\aa
‘/home/.trash/\\home\\aa’ -> ‘/home/aa’
$ cleartrash
clear sure?[n]y
removed directory: ‘/home/.trash/\\home\\bb’
$ rd /home/cc
removed directory: ‘/home/.trash/\\home\\user\\tangyy\\cc’
其中rm与rm -rf的功能相同,主要是为了迎合用户删除文件夹时,使用rm -rf的习惯。
为了在恢复回收站中文件的时候,能够将文件恢复到原有的路径下,所以将回收站中文件的文件名改为路径与文件名的结合,并且将'/'目录分层改为'\\',不然的话在回收站中也会产生目录分层。
最后在crontab中添加定时任务,每周定时清空回收站中的文件:
00 00 * * 1 sh /home/tools/cleartrash.sh > /home/tools/cleartrash.log
其中/home/tools/cleartrash.sh文件的实现代码如下:
#! /bin/bash
source /etc/bashrc
cleartrash_direct
不直接执行cleartrash_direct命令的原因是,crontab定时任务使用的环境变量和别名alias不是用户的,而是crontab单独设置的,所以需要先添加环境变量与别名source /etc/bashrc。
到这里回收站的基本功能已经实现完成,但目前发现了新的问题,就是使用‘rm a*’带*的命令时,不支持回收站,会报错。下面该解决这个问题了。