1. 程式人生 > >Linux Shell腳本生產環境下安全地刪除文件

Linux Shell腳本生產環境下安全地刪除文件

rm safe

腳本編寫背景

無論是生產環境、測試環境還是開發環境,經常需要使用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這樣的參數

參考:

這種事別人早就有做好的了,雖然學習的過程需要積累和歷練,但人生苦短,有時也需要站在巨人的肩膀上。

  1. 防止rm誤操作的方法

  2. 更改rm命令為移動到回收站

  3. trash-cli 0.17.1.14》將rm變成移動到回收站的Python實現

tag:safe-rm,trash-cli,rm

--end--

本文出自 “通信,我的最愛” 博客,請務必保留此出處http://dgd2010.blog.51cto.com/1539422/1943170

Linux Shell腳本生產環境下安全地刪除文件