Linux Shell脚本生产环境下安全地删除文件

脚本编写背景

无论是生产环境、测试环境还是开发环境,经常需要使用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="[email protected]"
        # 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 [email protected]
    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 [email protected]",
    "parameter": "[email protected]"
}

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 [email protected]
        else
            parameter_array="[email protected]"
            # 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 [email protected]
                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 [email protected]

# debug option
if ${DEBUG} ; then
    export PS4=${old_PS4}
    ${_XTRACE_FUNCTIONS}
fi

可以继续做的事情

1.根据操作日志方便的列出删除的文件

2.根据操作日志更方便的恢复文件

3.处理好文件不存在的情况

4.增加直接删除的情况

5.保留权限的问题

6.从log_file中的operation和parameter中去除-rf这样的参数

参考:

这种事别人早就有做好的了,虽然学习的过程需要积累和历练,但人生苦短,有时也需要站在巨人的肩膀上。

  1. 防止rm误操作的方法

  2. 更改rm命令为移动到回收站

  3. trash-cli 0.17.1.14》将rm变成移动到回收站的Python实现

tag:safe-rm,trash-cli,rm

--end--

时间: 2024-10-18 10:10:55

Linux Shell脚本生产环境下安全地删除文件的相关文章

shell脚本gbk环境下搜索utf-8日志

方法1: 使用linux iconv将utf-8日志转为gbk编码的文件,然后gbk环境下统计数据. utf8(){ LOG_FILE="/lcims/crontab_shell/outfile/lan_wlan_wo/SocketMain.log141114_lan1" LOG_FILE_TMP="/lcims/crontab_shell/outfile/lan_wlan_wo/141114_lan1" echo "utf-8-----" #o

Windows 2008下 rman backup scirpts(备份脚本)--已经在生产环境下通过验证

一.任务计划 windows -控制面板-管理工具-计划任务程序,右击"任务计划程序库","创建任务" 二:相关脚本中的内容: 2.1  back_rman.bat中的内容: set ORACLE_SID=FS F:\oracle\product\10.2.0\db_1\BIN\rman target / cmdfile=H:\worksql\windows_backupscript\backup.rcv log=H:\worksql\windows_backups

Linux shell脚本基础学习详细介绍(完整版)一

Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提.1. Linux 脚本编写基础◆1.1 语法基本介绍 1.1.1 开头 程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh 符号#!用来告诉系统它后面的参数是用来执行该文件的程序.在这个例子中我们使用/bin/sh来执行程序. 当编辑好脚本时,如果要执行该脚本,还必须使其可执行. 要使脚本可执

LINUX SHELL脚本攻略笔记[速查]

Linux Shell脚本攻略笔记[速查] 资源 shell script run shell script echo printf 环境变量和变量 pgrep shell数学运算 命令状态 文件描述符和重定向 cat 数组和关联数组 alias date 调试脚本 函数和参数 管道 读取命令输出 read 字段分隔符和迭代器 循环 比较和测试 find xargs tr md5sum sha1sum 对目录进行校验 sort uniq tempfile split bash变量匹配切分 exp

LInux Shell 脚本笔记

*************************** list.txt : 目录 ******************************** 1. variable.sh2. condition.sh3. control.sh4. AndOr.sh5. statementBlock.sh6. function.sh7. command_1.sh8. old_version_set.sh9. new_version_set.sh10. command_2.sh11. command_3.s

Linux shell脚本流程控制

博主搬家至51CTO,初来乍到,请多指教. 此次我们来通过实例解析Linux shell脚本流程控制 Linux shell脚本流程控制可分为三类:顺序执行,条件选择执行,循环执行 顺序执行:简单理解就是逐行执行脚本内容,逐行解读,逐行执行.(此处不做实例解析) 条件选择执行:可以理解为先进行某一条件的判断选择,再决定执行怎样的脚本内容.常见语句if case 条件选择语句:if if语句用法: 单分支 if 判断条件;then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件

Linux Shell脚本攻略(1.2)

1.2 终端打印 终端是交互式工具,用户可以通过它与shell环境进行交互.在终端中打印文本是大多数shell脚本和工具日常需要执行的基本任务.通过终端打印,人们可以知道系统的运行状态,这对用户来说是至关重要的. echo终端打印 echo "Welcome to Bash" echo 'Welcome to Bash' echo Welcome to Bash 以上三种方法的效果是一样的,输出内容都是"Welcome to Bash",并在末尾添加换行符.在默认情

Linux Shell脚本编程学习笔记和实战

http://www.1987.name/141.html shell基础 终端打印.算术运算.常用变量 Linux下搜索指定目录下特定字符串并高亮显示匹配关键词 从键盘或文件中获取标准输入 [read命令] 文件的描述符和重定向 数组.关联数组和别名使用 函数的定义.执行.传参和递归函数 条件测试操作与流程控制语句 获取时间日期格式和延时 [date.sleep命令] 内部字段分隔符IFS和脚本的调试DEBUG 显示.读取或拼接文件内容 [cat命令] 文件查找与打印文件列表 [find命令]

(linux shell)第一章--小试牛刀(下)

文章来源: (linux shell)第一章--小试牛刀(下) 1.6 数组和关联数组 1.6.1 预备知识 Bash同一时候支持普通数组和关联数组.普通数组仅仅能使用整数作为数组索引,而关联数组能够使用字符串作为数组索引.关联数组在非常多操作中相当实用. 1.6.2 实战演练 定义数组的方法有非常多,能够在单行中使用一列值来定义一个数组: array_var=(1,2,3,4,5,6)   #这些值将会存储在以0为起始索引的连续位置上 另外.还能够将数组定义成一组索引-值: array_var