脚本编写背景
无论是生产环境、测试环境还是开发环境,经常需要使用rm命令删除&批量一些“重要”目录下的文件。按照Linux的哲学“小即是美”(一个程序只做一件事)+“用户清楚自己做什么”(用户知道自己想要什么,也明白自己在做什么,并且会为自己的行为负责),那么用户在执行rm时,一定要知道自己的操作可能引起的后果,因此“三思而后行”真的很重要。但这对于一部分人来讲,真的可能是灾难性的,手抖、手贱和任何错误的操作(在路径分隔符"/"前多打了空格,错误使用*,错误的路径(当前路径、相对路径、错误的操作路径)、错误脚本(竟然有人敢在生产环境调试未经过测试的脚本)、错误的批量操作、错误的安全策略)都可能引起数据的丢失,这在生产环境,特别是缺少备份缺少热备的生产环境中是非常致命非常可怕的。
因此通过脚本或者其他方式来让用户后悔一次是非常有必要的。本文用Bash Shell Script的方式实现这一问题,其他解决方案可以参见文末的“参考”。
脚本编写思路
跟回收站的思路比较相似,回收站有这样的特性:1.并非真的删除;2.同名的可以;3.记住文件的源路径,作为脚本也应该做到如此
脚本使用起来应该跟rm使用起来基本类似
脚本使用截图
关于rm的参数问题
脚本内使用忽略的策略,无论使用rm的哪种参数,都将进行mv操作,此处宜当改进。
脚本内容
脚本内容可以参见GitHub.
#!/usr/bin/env bash
# a command or alias to replace ‘rm‘ with a safe and easy to use, safe remove files or directories
# See also:
# safe-rm - wrapper around the rm command to prevent accidental deletions - https://github.com/kaelzhang/shell-safe-rm
# trash-cli - Command line interface to FreeDesktop.org Trash - https://pypi.python.org/pypi/trash-cli/
# debug option
DEBUG=false # DEBUG=true
if ${DEBUG} ; then
old_PS4=$PS4
# export PS4=‘+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: ‘
export PS4=‘+${LINENO}: ${FUNCNAME[0]}: ‘ # if there is only one bash script, do not display ${BASH_SOURCE}
_XTRACE_FUNCTIONS=$(set +o | grep xtrace)
set -o xtrace
fi
# set an empty function using for location this line quickly in PyCharm editor on purpose.
function _empty() { return; }
# Public header
# =============================================================================================================================
# resolve links - $0 may be a symbolic link
# learn from apache-tomcat-6.x.xx/bin/catalina.sh
PRG="$0"
while [ -h "$PRG" ]; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : ‘.*-> \(.*\)$‘`
if expr "$link" : ‘/.*‘ > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
# Get standard environment variables
PRGDIR=`dirname "$PRG"`
# echo color function, smarter, learn from lnmp.org lnmp install.sh
function echo_r (){
# Color red: Error, Failed
[ $# -ne 1 ] && return 1
echo -e "\033[31m$1\033[0m"
}
function echo_g (){
# Color green: Success
[ $# -ne 1 ] && return 1
echo -e "\033[32m$1\033[0m"
}
function echo_y (){
# Color yellow: Warning
[ $# -ne 1 ] && return 1
echo -e "\033[33m$1\033[0m"
}
function echo_b (){
# Color blue: Debug Level 1
[ $# -ne 1 ] && return 1
echo -e "\033[34m$1\033[0m"
}
function echo_p (){
# Color purple,magenta: Debug Level 2
[ $# -ne 1 ] && return 1
echo -e "\033[35m$1\033[0m"
}
function echo_c (){
# Color cyan: friendly prompt, Level 1
[ $# -ne 1 ] && return 1
echo -e "\033[36m$1\033[0m"
}
# end echo color function, smarter
#WORKDIR="`realpath ${WORKDIR}`"
WORKDIR="`readlink -f ${PRGDIR}`"
# end public header
# =============================================================================================================================
real_rm=‘/bin/rm‘
trash_dir="$HOME/.trash" # if do not use "$HOME" or "~" to resolve permission problem, should use "chmod o+t $trash_dir" .chmod --help: Each MODE is of the form `[ugoa]*([-+=]([rwxXst]*|[ugo]))+‘.
log_dir="$trash_dir"
log_file="$log_dir/operation.log"
trash_save_days=3
function real_rm() {
if [[ ! -f ${real_rm} ]]; then
echo ‘safe-rm cannot find the real "rm" binary‘
exit 1
fi
save_days=${trash_save_days:-10}
test $(find -L /tmp/.delete/ -type d ! -name "^." -a ! -wholename "/tmp/.delete/" -mtime +${save_days} -exec echo ‘{}‘ \; | wc -l ) -gt 0
found_old_files=$?
if [[ ${found_old_files} -eq 0 ]]; then
echo_b "old files found, cleaning"
#find -L ${trash_dir}/ -maxdepth 1 -type d ! -name "^." -mtime +${save_days} -exec rm -rf ‘{}‘ \;
find -L ${trash_dir}/ -maxdepth 1 -type d ! -wholename "$trash_dir/" ! -name "^." -mtime +${save_days} -exec rm -rf ‘{}‘ \;
echo_g "old files cleaned successfully"
else
echo_g "old files in standby state, passed"
fi
}
function safe_rm() {
if [[ "$1x" = ‘x‘ ]]; then
${real_rm} --help
exit 1
fi
if [[ ! -d ${trash_dir} ]]; then
mkdir -p ${trash_dir}
fi
# date +%Y%m%d%H%M%S.%N | shasum | awk ‘{print $1}‘ | cat - -A
uniq_trash_dir="$trash_dir/$(date +%Y%m%d%H%M%S.%N | shasum | awk ‘{print $1}‘)"
mkdir -p ${uniq_trash_dir}
if [[ $# -eq 1 ]];then
if [[ -f $1 ]] || [[ -d $1 ]]; then # ignore rm -f|-r|-rf|-fr, etc
mv $1 ${uniq_trash_dir}
retval=$?
fi
else
# alternative impl of ‘rm FILE...‘
parameter_array="$@"
# IFS=‘ ‘$‘\t‘$‘\n‘, IFS=$‘ \t\n‘, If IFS is unset, or its value is exactly <space><tab><newline>
old_IFS=$IFS
IFS=‘ ‘$‘\t‘$‘\n‘
for parameter in ${parameter_array}; do
if [[ -f ${parameter} ]] || [[ -d ${parameter} ]]; then # ignore rm -f|-r|-rf|-fr, etc
mv ${parameter} ${uniq_trash_dir}
fi
done
retval=$?
IFS="$old_IFS"
fi
log_operation $@
exit ${retval}
}
function log_operation(){
tee -a ${log_file}<<-eof # debug purpose or notify mode
{
"date_human": "$(date +‘%Y-%m-%d %H:%M:%S.%N‘)",
"date": "$(date)",
"user": "$USER",
"ssh_client": "$SSH_CLIENT",
"ssh_connection": "$SSH_CONNECTION",
"ssh_tty": "$SSH_TTY",
"trash_dir": "${uniq_trash_dir}"
"log_file":"${log_file}",
"pwd": "$PWD",
"operation": "$0 $@",
"parameter": "$@"
}
eof
}
function usage(){
cat - << eof
${WORKDIR}/`basename $0` help show help message
${WORKDIR}/`basename $0` clean clean old deleted files
eof
}
function main(){
lock_filename="lock_$$_${RANDOM}"
# lock_filename_full_path="/var/lock/subsys/$lock_filename"
lock_filename_full_path="/var/lock/$lock_filename"
if ( set -o noclobber; echo "$$" > "$lock_filename_full_path") 2> /dev/null;then
trap ‘rm -f "$lock_filename_full_path"; exit $?‘ INT TERM EXIT
[ ! -x ${WORKDIR}/`basename $0` ] && chmod +x ${WORKDIR}/`basename $0`
if [[ $# -lt 1 ]]; then
${WORKDIR}/`basename $0` help
exit 0
fi
if [ -f $1 ]; then
safe_rm $@
else
parameter_array="$@"
# IFS=‘ ‘$‘\t‘$‘\n‘, IFS=$‘ \t\n‘, If IFS is unset, or its value is exactly <space><tab><newline>
old_IFS=$IFS
IFS=‘ ‘$‘\t‘$‘\n‘
for parameter in ${parameter_array}; do
if [[ -f ${parameter} ]] || [[ -d ${parameter} ]]; then # ignore rm -f|-r|-rf|-fr, etc
safe_rm $@
fi
done
IFS="$old_IFS"
fi
case $1 in
clean)
real_rm
;;
help|*)
usage
exit 1
;;
esac
rm -f "$lock_filename_full_path"
trap - INT TERM EXIT
else
echo "Failed to acquire lock: $lock_filename_full_path"
echo "held by $(cat ${lock_filename_full_path})"
fi
}
main $@
# debug option
if ${DEBUG} ; then
export PS4=${old_PS4}
${_XTRACE_FUNCTIONS}
fi可以继续做的事情
1.根据操作日志方便的列出删除的文件
2.根据操作日志更方便的恢复文件
3.处理好文件不存在的情况
4.增加直接删除的情况
5.保留权限的问题
6.从log_file中的operation和parameter中去除-rf这样的参数
参考:
这种事别人早就有做好的了,虽然学习的过程需要积累和历练,但人生苦短,有时也需要站在巨人的肩膀上。
tag:safe-rm,trash-cli,rm
--end--
本文出自 “通信,我的最爱” 博客,请务必保留此出处http://dgd2010.blog.51cto.com/1539422/1943170
原文:http://dgd2010.blog.51cto.com/1539422/1943170