目录

[TOC]


新版

不要用我这个"垃圾"脚本了,发现2007年(Tue Jul 24 11:46:14 2007 +0000)就有人做过了,而且还更好!

我在想怎么每个磁盘都做一个垃圾桶目录的时候,就发现了这个现成的仓库。。

这是github地址: https://github.com/andreafrancia/trash-cli

这里是使用说明: linux垃圾箱防止误删


(以下旧版已废弃)

效果展示


一、问题描述

有时候我们会误删一些文件,这时候我们可以使用回收站来恢复误删的文件。


二、偷懒解决方案(一键脚本)

觉得麻烦也可以直接复制如下代码进入终端回车即可,效果和不偷懒方案一样的

wget -qO remove.sh https://git.dreamdusk.com/Dean/new_rm/raw/branch/main/new_rm.sh && chmod +x remove.sh && ./remove.sh

运行以后输入rm -h查看帮助

三、不偷的懒解决方案

将本文件复制到任意位置(名字任意,比如是XXX),然后执行 chmod +x XXX,最后执行一次 bash XXX 即可。(如果自动安装失败的话,也可以手动:1.在你使用的shell的配置文件( ~. bashrc 或 ~.zshrc)中添加一行 alias rm='/usr/local/bin/new_rm', 2.将此脚本重命名为 new_rm,并复制到 /usr/local/bin/ 目录下, 3.重新开启一个终端输入rm即可。)

其实只是把安装一键脚本的过程手动化了,,,反正就是运行一次下面的脚本就安装好了,要卸载就rm -uninstall
#!/bin/bash

# 项目: new_rm
# 作者: BrettDean
# 说明: 一个用于替代系统自带的 rm 命令的脚本,能够将删除的文件移动到回收站中,以便于恢复。
# 依赖: crontab
# 安装: 将本文件复制到任意位置(名字任意,比如是XXX),然后执行 `chmod +x XXX`,最后执行一次 `bash XXX` 即可。(如果自动安装失败的话,也可以手动:1.在你使用的shell的配置文件( ~. bashrc 或 ~.zshrc)中添加一行 `alias rm='/usr/local/bin/new_rm'`, 2.将此脚本重命名为 `new_rm`,并复制到 `/usr/local/bin/` 目录下, 3.重新开启一个终端输入rm即可。)
# 卸载: 执行 `rm -uninstall` 即可。
# 更新: 如果有新版的话,直接再运行一次新版的脚本即可(也可按照这个方法恢复旧版,只要你运行的不是`/usr/local/bin/new_rm`,那么就会更新`/usr/local/bin/new_rm`为你当前运行的版本)。
# 日期: 2023-04-26
# 版本: 1.0.9
VERSION=1.0.9

# 帮助文本
usage() {
  echo ""
  echo "用法: rm [选项] 文件(一个或多个,用空格分隔)"
  echo ""
  echo "可用选项:"
  echo "  -config     --configeration    显示回收站的配置信息"
  echo "  -cron       --crontab          设置定时清空回收站"
  echo "  -del        --delete           删除垃圾桶中的指定文件"
  echo "  -e          --empty            清空回收站所有文件"
  echo "  -f   -rf    --force            真正删除指定的文件或目录"
  echo "  -h          --help             显示此帮助信息并退出"
  echo "  -ls   -l    --list             显示回收站中的所有文件"
  echo "  -ra         --restore-all      恢复垃圾桶目录中的所有文件"
  echo "  -re         --restore          尝试恢复指定的文件或目录"
  echo "  -set        --set-path         修改垃圾桶路径"
  echo "  -uninstall  --uninstall        卸载本脚本"
  echo "  -update     --update           更新本脚本"
  echo "  -v          --version          显示版本信息并退出"
  echo ""
  echo "示例:rm /path/to/file 或者 rm -r /path/to/file 将指定的文件或目录移入回收站"
}

# 初始化运行环境
function init_new_rm() {

  # '''
  # 该函数主要功能是初始化一个名为 `new_rm` 的别名,将其指向 `/usr/local/bin/new_rm`,以替代系统自带的 `rm` 命令。该脚本能够自动识别用户当前使用的 shell 类型(Bash 或 Zsh),并自动在用户的 rc(runtime configuration)文件中添加或修改相应的别名定义。以下是该脚本的详细解释:

  # 1.定义函数 `init_new_rm()`,用于初始化 `new_rm` 别名。
  # 2.通过命令 `grep "^$USER:" /etc/passwd | cut -d: -f7` 获取当前用户的默认 shell,赋值给变量 $DEFAULT_SHELL。
  # 3.如果用户的默认 shell 是 Bash,则设置变量 `$shell` 为 "bash",并通过命令 `grep -v '.*#.*' ~/.bashrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l` 统计当前用户在 Bash 中定义的所有别名数量,赋值给变量 `$alias_number`。如果用户的默认 shell 是 Zsh,则设置变量 `$shell` 为 "zsh",并通过命令 `grep -v '.*#.*' ~/.zshrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l` 统计当前用户在 Zsh 中定义的所有rm别名数量,赋值给变量 `$alias_number`。
  # 4.如果用户的默认 shell 不是 Bash 或 Zsh,则输出错误信息并退出脚本。
  # 5.将变量 `$shell` 与字符串 "rc" 进行拼接,得到用户的 rc 文件路径,赋值给变量 `$rc_file`。
  # 6.如果 `/usr/local/bin/new_rm` 不存在,则将当前脚本复制至该路径下。
  # 7.如果 `/usr/local/bin/new_rm` 存在,且别名数量为 0(即用户没有定义任何别名),则在用户的 rc 文件中添加一行 `alias rm='/usr/local/bin/new_rm'`,以初始化 `new_rm` 别名。
  # 8.如果 `/usr/local/bin/new_rm` 存在,且别名数量为 1,则判断该别名是否正确。如果别名正确,则不需要再次初始化;否则,删除 rc 文件中所有关于 `rm` 的别名定义,并重新添加一行正确的 `alias rm='/usr/local/bin/new_rm'`,最后再次调用 `init_new_rm()` 函数进行初始化。
  # 9.如果 `/usr/local/bin/new_rm` 存在,且别名数量大于 1,则删除 rc 文件中所有关于 `rm` 的别名定义,并重新添加一行正确的 `alias rm='/usr/local/bin/new_rm'`,最后再次调用 `init_new_rm()` 函数进行初始化。
  # '''

  # 判断默认 shell
  DEFAULT_SHELL=$(grep "^$USER:" /etc/passwd | cut -d: -f7)
  if echo "$DEFAULT_SHELL" | grep -q "bash"; then
    # echo "默认 shell 是 Bash"
    shell="bash"
    # 获取bash中定义的所有别名数量
    alias_number=$(grep -v '.*#.*' ~/.bashrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l)
  elif echo "$DEFAULT_SHELL" | grep -q "zsh"; then
    # echo ""
    # echo ""
    # echo ""
    # echo "默认 shell 是 Zsh"
    shell="zsh"
    # 获取zsh中定义的所有别名数量
    alias_number=$(grep -v '.*#.*' ~/.zshrc | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | wc -l)
  else
    # echo "默认 shell 是 $DEFAULT_SHELL"
    echo "暂不支持 $DEFAULT_SHELL"
    exit 1
  fi
  declare shell=$shell

  # 拼接rc配置文件
  rc_file="$HOME/."$shell"rc"
  init_trash_path_and_time_interval

  # 如果 $TRASH_DIR 不存在的话就创建它
  if [ ! -d "$TRASH_DIR" ]; then
    mkdir -p "$TRASH_DIR"
  fi

  # 在上面的代码中,已经获取了 alias_number和shell和rc_file,他们分别是别名数量、shell类型和rc配置文件
  # echo 'shell='$shell
  # echo 'alias_number='$alias_number
  # echo 'rc_file='$rc_file

  # 如果/usr/local/bin/new_rm存在
  if [ -e /usr/local/bin/new_rm ]; then
    # 如果当前运行的脚本不是 /usr/local/bin/new_rm
    if [[ "$0" != "/usr/local/bin/new_rm" ]]; then
      # 删除/usr/local/bin/new_rm
      rm -rf /usr/local/bin/new_rm
    fi
  fi
  # 如果/usr/local/bin/new_rm不存在,则将本文件复制过去
  if [ ! -e /usr/local/bin/new_rm ]; then
    # echo "/usr/local/bin/new_rm不存在,已将本文件复制过去"
    cp -f $0 /usr/local/bin/new_rm
    chmod +x /usr/local/bin/new_rm
  fi

  # 如果/usr/local/bin/new_rm存在,但是别名数量 等于 0,则在 $rc_file 中添加别名
  if [ -e /usr/local/bin/new_rm ] && [ $alias_number -eq 0 ]; then
    # echo "/usr/local/bin/new_rm存在;rc文件中缺少别名"
    # 在 $rc_file 中添加一行 `alias rm='/usr/local/bin/new_rm'`
    # echo "向 $rc_file 中添加正确的别名1"
    echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
    is_add_alias=1
  # fi
  # 如果/usr/local/bin/new_rm存在,且别名数量 等于 1,则不需要初始化,直接退出初始化函数
  elif [ -e /usr/local/bin/new_rm ] && [ $alias_number -eq 1 ]; then # 虽然经过上面的if,已经知道这里/usr/local/bin/new_rm肯定存在,但是还是写一下
    # echo "/usr/local/bin/new_rm存在;关于rm的alias别名总数量 等于 1"
    # 如果别名数量为1,且别名正确`alias rm='/usr/local/bin/new_rm'`,则不需要初始化
    if [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then

      # echo "别名为\`alias rm='/usr/local/bin/new_rm'\`,别名正确"
      # echo "rc文件中的rm正确"
      # echo "一切正常,不需要初始化"
      return
    # else
    #     echo "别名错误"
    fi
    # 如果别名数量为1,但别名不正确`alias rm='/usr/local/bin/new_rm'`,则需要初始化
    if ! [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
      # else
      # echo "别名不正确,开始自动修改"
      # echo "删除$rc_file中的所有关于rm的alias别名定义"
      sed -i "/^[[:space:]]\{0,\}\balias\b[[:space:]]\{1,\}[\"\'\`]\?\brm\b[\"\'\`]\?/d" "$rc_file"
      # echo "向 $rc_file 中添加一行正确的别名2"
      echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
      is_add_alias=1
      # 再次初始化
      # echo "再次初始化1"
      init_new_rm
    fi

  # 如果/usr/local/bin/new_rm存在,且别名数量 大于 1,则删除所有关于rm的alias别名定义,然后添加一行`alias rm='/usr/local/bin/new_rm'`
  elif [ -e /usr/local/bin/new_rm ] && [ $alias_number -gt 1 ]; then
    # echo "/usr/local/bin/new_rm存在;关于rm的alias别名总数量 大于 1"
    # 删除~/.zshrc或者~/.bashrc中的所有关于rm的alias别名定义
    # echo "删除$rc_file中的所有关于rm的alias别名定义"
    sed -i '/ *alias *rm=/d' $rc_file
    # echo "向 $rc_file 中添加一行正确的别名3"
    echo "alias rm='/usr/local/bin/new_rm'" >>$rc_file
    # 再次初始化
    # echo "再次初始化2"
    init_new_rm
  fi
  # echo "再次初始化"
  # echo "is_add_alias=$is_add_alias"
  init_new_rm
}

function init_trash_path_and_time_interval() { # 初始化垃圾桶路径和清理垃圾桶的时间间隔
  # 如果 crontab 中对应的注释和cron都唯一存在
  if [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 1 ]] && [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 1 ]]; then # 如果crontab中对应的注释和cron都唯一存在
    # 提取crontab中注释行中的垃圾桶路径和cron表达式中的两个垃圾桶路径分别赋值给path0,path1,path2
    path0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:"(.*)"$/\1/g')
    path1=$(crontab -l | grep -Eo "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true" | sed -E "s/0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true/\1/g")
    path2=$(crontab -l | grep -Eo "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true" | sed -E "s/0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A (.*?)\\)\" \] && rm -rf (.*?)\/\* \\|\\| true/\2/g")
    # 对比path0和path1,path2是否相等
    if [[ $path0 == $path1 ]] && [[ $path0 == $path2 ]]; then # 如果三个路径都相等
      # 声明全局变量垃圾桶路径
      declare -g TRASH_DIR=$path0
      # 获取时间间隔
      time0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g')                                                                                                      # 提取crontab中注释行中的时间间隔
      time1=$(crontab -l | grep -Eo "0 \*\/([0-9]+) \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true" | sed -E "s,0 \*/([0-9]+) \* \* \* \[ -n \"\\\$\\(ls -A /.*?\\)\" \] && rm -rf /.*?/\* \|\| true,\1,g") # 提取crontab中cron表达式中的时间间隔
      if [[ $time0 != $time1 ]]; then                                                                                                                                                                                                   # 如果两个时间间隔不相等
        echo "因为两个时间间隔不相等,故重置为默认值!"
        init_default_crontab
      # 如果两个时间间隔相等,但是不是1-23之间的整数,则重置为默认值
      elif [[ $time0 == $time1 ]] && [[ $time0 -lt 1 ]] || [[ $time0 -gt 23 ]]; then # 如果两个时间间隔相等,但是不是1-23之间的整数
        echo "因为crontab中的时间间隔不合法,故重置为默认值!"
        init_default_crontab
      else
        # echo "以前已正确设置过垃圾桶路径,跳过路径初始化"
        : # 什么都不做
      fi
    elif [[ $path0 != $path1 ]] || [[ $path0 != $path2 ]]; then # 如果三个路径有一个不相等
      echo "虽然crontab 中对应的注释和cron都唯一存在,但是注释行中的垃圾桶路径和cron表达式中的两个垃圾桶路径不一致,故重置为默认值!"
      init_default_crontab
    fi
  # 如果 crontab 中对应的注释和cron不存在
  elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 0 ]] && [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 0 ]]; then # 如果 crontab 中对应的注释和cron不存在
    echo "crontab 中对应的注释和cron都不存在,故初始化为默认值!"
    init_default_crontab
  # 如果 crontab 中对应的注释和cron存在但不唯一
  elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -gt 1 ]] || [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -gt 1 ]]; then # 如果 crontab 中对应的注释和cron存在但不唯一
    echo "crontab 中对应的注释和cron存在但不唯一,故重置为默认值!"
    init_default_crontab
  # 如果 crontab 中对应的注释或者cron只有一个存在
  elif [[ $(crontab -l | grep -cE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") -eq 1 ]] || [[ $(crontab -l | grep -cE '^0 \*/[0-9]+ \* \* \* \[ -n "\$\(ls -A \/.*?\)" \] && rm -rf \/.*?\/\* \|\| true$') -eq 1 ]]; then # 如果 crontab 中对应的注释或者cron只有一个存在
    echo "crontab 中对应的注释或者cron只有一个存在,故重置为默认值!"
    init_default_crontab
  else
    echo -e "未知错误!请手动运行 crontab -e ,并 删除 其中类似于下面的两行,然后重新运行!\n"
    echo "# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:\"/home/Trash\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
    echo "0 */23 * * * [ -n \"\$(ls -A /home/Trash)\" ] && rm -rf /home/Trash/* || true"
    exit 1
  fi
}

function no_macOS() {
  # 如果操作系统是macOS则直接报错退出
  if [[ "$OSTYPE" == "darwin"* ]]; then
    echo echo "本脚本不支持 macOS 系统,因为 macOS 系统自带的 bash 版本为 3.2.57,不支持 'declare -g' 命令,至少要 4.0 版本的 bash 才支持。"
    exit 1
  fi
}

# 初始化crontab为默认值
function init_default_crontab() {
  # 先删除原来错误的crontab注释和任务
  (crontab -l | grep -vE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") | crontab -
  (crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
  # 再重置为默认值
  (
    crontab -l 2>/dev/null
    echo "# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:\"/home/Trash\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
  ) | crontab -
  (
    crontab -l 2>/dev/null
    echo "0 */23 * * * [ -n \"\$(ls -A /home/Trash)\" ] && rm -rf /home/Trash/* || true"
  ) | crontab -
  # 声明全局变量默认垃圾桶路径
  declare -g TRASH_DIR="/home/Trash"
  echo "初始化完成,使用 rm -h 查看帮助"
  echo "默认垃圾桶路径为:$TRASH_DIR"
  echo "默认每隔23小时清理一次垃圾桶"
}

function update_TRASH_DIR_in_function_set_path() {
  TRASH_DIR=$(readlink -f "$1") # 获取绝对路径,防止用户输入的是相对路径
  # 更新crontab中的垃圾桶目录和$TRASH_DIR变量
  declare -g TRASH_DIR="$TRASH_DIR"
  time0=$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g') # 提取crontab中注释行中的时间间隔
  # 先删除原来错误的crontab注释和任务
  (crontab -l | grep -vE "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"") | crontab -
  (crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
  # 再更新
  # (crontab -l 2>/dev/null; echo '# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:"/home/Trash",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!') | crontab -
  # (crontab -l 2>/dev/null; echo '# 每隔23小时自动清理垃圾桶,当前垃圾桶路径:"/home/Trash",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!') | crontab -

  (
    crontab -l 2>/dev/null
    echo "# 每隔$time0小时自动清理垃圾桶,当前垃圾桶路径:\"$TRASH_DIR\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则/usr/local/bin/new_rm会出问题!"
  ) | crontab -
  (
    crontab -l 2>/dev/null
    echo "0 */$time0 * * * [ -n \"\$(ls -A $TRASH_DIR)\" ] && rm -rf $TRASH_DIR/* || true"
  ) | crontab -
  echo "已将新的垃圾桶路径设置为:$TRASH_DIR"
}

function check_before_rm() {
  # 判断根路径/下的所有文件和文件夹(不包含.开头的隐藏文件)组成的数组是否为用户输入的所有参数组成的数组的子集,如果是子集,那么很有可能参数中包含了危险的`^\/\*$`,也就是单独的`/*`
  # 这样拐着弯判断的原因是:当你在命令行中输入 * 时,它会被 shell 解释成通配符(wildcard),表示匹配当前目录下的所有文件和子目录。因此,在你运行脚本时,* 已经被 shell 扩展为一个文件列表,而不是原始字符 *。
  # 本来应该是先判断:当`/`数量小于2的时候,对比正则表达式`^(?:\.|\/)(?:\.\/)+|.*\/+\.?${root_files_sorted[$i]}$`,但是最后失败了,直接用三种情况来代替吧,如果有人硬是要输入`rm ////////*`或者`rm /./././////.//.*`这种的话,我也没办法了
  # 获取根目录下的所有文件和文件夹并排序
  root_files_sorted=($(ls -1 / | sort))                    # 前面什么都没有的
  root_files_sorted_2=($(ls -1 / | sort | sed 's/^/.\//')) # 前面加了个`./`的
  root_files_sorted_3=($(ls -1 / | sort | sed 's/^/\//'))  # 前面加了个`/`的

  # 将用户输入的参数按字母顺序排序并保存到数组 args_sorted 中
  args_sorted=($(printf '%s\n' "$@" | sort))
  #循环检测args_sorted中的每个元素是否在单独的`/`,则报错并exit 1
  for i in "${args_sorted[@]}"; do
    if [[ $i == "/" ]]; then
      echo "不允许删除根目录!"
      exit 1
    fi
  done

  # 定义函数来判断子集
  function is_subset() {
    local root_files=("$@")
    local is_subset=true
    # 如果 root_files 数组元素数量大于 args_sorted 数组元素数量,则不可能为子集
    if [[ ${#root_files[@]} -gt ${#args_sorted[@]} ]]; then
      is_subset=false
    else
      # 定义指向两个数组的指针
      i=0
      j=0
      # 采用类似于归并排序的方式遍历两个数组
      while [[ $i -lt ${#root_files[@]} && $j -lt ${#args_sorted[@]} ]]; do
        # 如果 root_files[i] 比 args_sorted[j] 小,那么就需要再比较 root_files[i+1] 是否在 args_sorted 中存在
        if [[ ${root_files[$i]} < ${args_sorted[$j]} ]]; then
          found=false
          for k in $(seq $i ${#root_files[@]}); do
            if [[ ${root_files[$k]} == ${args_sorted[$j]} ]]; then
              found=true
              i=$k
              break
            fi
          done
          if [ "$found" == false ]; then
            is_subset=false
            break
          fi
        # 如果 root_files[i] 比 args_sorted[j] 大,那么就需要再比较 args_sorted[j+1] 是否在 root_files 中存在
        elif [[ ${root_files[$i]} > ${args_sorted[$j]} ]]; then
          found=false
          for k in $(seq $j ${#args_sorted[@]}); do
            if [[ ${args_sorted[$k]} == ${root_files[$i]} ]]; then
              found=true
              j=$k
              break
            fi
          done
          if [ "$found" == false ]; then
            is_subset=false
            break
          fi
        # 如果两个元素相等,那么就直接将指针右移一位
        else
          i=$((i + 1))
          j=$((j + 1))
        fi
      done
    fi

    echo $is_subset
  }

  # 判断三种情况下根目录是否为用户输入参数的子集
  is_subset_1=$(is_subset "${root_files_sorted[@]}")
  is_subset_2=$(is_subset "${root_files_sorted_2[@]}")
  is_subset_3=$(is_subset "${root_files_sorted_3[@]}")
  # 输出结果
  # echo "is_subset_1= $is_subset_1"
  # echo "is_subset_2= $is_subset_2"
  # echo "is_subset_3= $is_subset_3"
  if [[ $is_subset_1 == true || $is_subset_2 == true || $is_subset_3 == true ]]; then
    # echo "根目录是用户输入参数的子集"
    is_root_files_subset_args=1
  else
    # echo "根目录不是用户输入参数的子集"
    is_root_files_subset_args=0
  fi

  # echo "root_files_sorted= ${root_files_sorted[*]}"
  # echo "      args_sorted= ${args_sorted[*]}"
  # echo "is_root_files_subset_args= $is_root_files_subset_args"

  if [[ $is_root_files_subset_args == 1 ]]; then
    read -p "你有可能正在删除根目录中的所有文件!!!!!!!确定要继续吗? [y/n]" choice
    case "$choice" in
    y | Y)
      # 用户选择继续执行,执行原始命令并以其返回码退出
      ;;
    *)
      # 其他任何输入,中止
      echo "用户已取消操作。"
      exit 1
      ;;
    esac
  fi
}
# 初始化成功以后检查当前运行的是不是/usr/local/bin/new_rm,如果是,则退出当前函数,否则,删除当前运行的脚本
function self_eraser() {
  # 如果当前运行的是/usr/local/bin/new_rm,则退出当前函数
  if [[ "$0" == "/usr/local/bin/new_rm" ]]; then
    return
  fi
  # 如果当前运行的不是/usr/local/bin/new_rm,则删除当前运行的脚本
  if [[ "$0" != "/usr/local/bin/new_rm" ]]; then
    rm -f $0
  fi
}

# 一键卸载本脚本的函数
function uninstall_new_rm() {
  #首先再次向用户询问是否卸载
  read -p "确定要卸载本脚本吗?[y/N]: " answer
  if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 如果用户选择卸载
    # 如果回收站不为空,则询问用户是否清空回收站
    if [[ -n "$(ls -A $TRASH_DIR)" ]]; then # 如果回收站不为空
      # echo "垃圾桶中所有文件:"
      # echo -e '大小    "删除时间 (文件类型:文件名)"\n'
      # du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||"| sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
      echo -e "以下是垃圾桶目录中的所有文件:\n"
      # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
      files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
      echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"
      echo -e "在卸载本脚本前,是否清空回收站?\n如果选择 [y],则会直接清空回收站,然后卸载本脚本\n如果选择 [n],则会在不清空回收站的情况下,直接卸载本脚本\n如果选择 [c],则会取消卸载"
      read -p "请选择 [y/N/c]: " answer
      if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 如果用户选择清空回收站
        # 清空垃圾桶目录
        rm -rf "${TRASH_DIR:?}/"*
        echo "已成功 清空 垃圾桶:${TRASH_DIR}"
      elif [[ "$answer" == "n" || "$answer" == "N" ]]; then # 如果用户选择不清空回收站
        echo "已取消清空回收站,将在保留回收站中的文件的情况下卸载本脚本"
        echo "回收站的路径为:${TRASH_DIR} 请在卸载完成后自行处理"
      elif [[ "$answer" == "c" || "$answer" == "C" ]]; then # 如果用户选择取消卸载
        echo "已取消卸载"
        exit 0
      else
        echo "输入错误,已取消卸载"
        exit 1
      fi
    fi
  else # 如果用户选择不卸载,只要不是y或者Y,都视为不卸载
    echo "已取消卸载"
    exit 0
  fi
  #正式开始卸载
  # 如果~/.zshrc或者~/.bashrc中存在关于rm的alias别名定义,则删除它
  if [[ "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
    sed -i "/^[[:space:]]\{0,\}\balias\b[[:space:]]\{1,\}[\"\'\`]\?\brm\b[\"\'\`]\?/d" "$rc_file"
  fi
  #删除crontab中的相关任务和注释
  (crontab -l | grep -vE "0 \*\/[0-9]+ \* \* \* \[ -n \"\\$\\(ls -A \/.*?\\)\" \] && rm -rf \/.*?\/\* \\|\\| true") | crontab -
  (crontab -l | grep -vE "# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\"\/.*?\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则\/usr\/bin\/rm_new会出问题!") | crontab -
  # 删除/usr/local/bin/new_rm
  rm -rf "/usr/local/bin/new_rm"
  #检查一下,如果/usr/local/bin/new_rm不存在,并且~/.zshrc或者~/.bashrc中不存在关于rm的alias别名定义,则说明卸载成功
  if [[ ! -e /usr/local/bin/new_rm ]] && [[ ! "$(grep -v '.*#.*' "$rc_file" | grep 'alias' | sed -E 's/^[[:space:]]\{0,\}\balias\b/alias/' | sed "s/[\'\"\`]//g")" =~ ^[\ ]*?alias[\ ]*?rm=\/usr\/local\/bin\/new_rm[\ ]*?$ ]]; then
    echo "已彻底卸载本脚本"
  else
    echo "不知道为什么卸载失败了"
    echo "请手动删除/usr/local/bin/new_rm"
    echo "并且删除~/.zshrc或者~/.bashrc中的关于rm的alias别名定义"
    echo “还有删除crontab中的相关任务和注释”
    echo "然后重启终端"
  fi
  $shell
}

# 这个函数的功能是检测参数是否符合要求,如果不符合要求则退出脚本
# 检测传入的参数是否合法
function parse_args() {
  local args=("$@") # 将所有参数存入 args 数组中
  # 定义合法的参数列表
  legal_args_1_dash_list=("-config" "-cron" "-del" "-e" "-f" "-rf" "-h" "-ls" "-l" "-ra" "-re" "-set" "-uninstall" "-update" "-v")
  legal_args_2_dash_list=("--configeration" "--crontab" "--delete" "--empty" "--force" "--help" "--list" "--restore" "--restore-all" "--set-path" "--uninstall" "--update" "--version")
  # 遍历所有参数,- 开头字符串存入 param_1_dash 数组中,-- 开头字符串存入 param_2_dash 数组中,其他字符串存入 param_others 数组中
  for param in "${args[@]}"; do
    # 如果当前参数的开头为 --
    if [[ "$param" == --* ]]; then
      param_2_dash+=("${param:2}")
    # 如果当前参数的开头为 -
    elif [[ "$param" == -* ]]; then
      param_1_dash+=("${param:1}")
    # 如果当前参数不以 - 或 -- 开头,则直接跳过
    else
      param_others+=("$param")
    fi
  done
  # echo "param_1_dash: ${param_1_dash[@]}" # 打印出所有以 - 开头的参数
  # echo "param_2_dash: ${param_2_dash[@]}" # 打印出所有以 -- 开头的参数
  # echo "param_others: ${param_others[@]}" # 打印出所有不以 - 或 -- 开头的参数

  # 遍历所有以 - 开头的参数,如果参数不在合法参数列表中,则报错
  for param in "${param_1_dash[@]}"; do
    if ! [[ "${legal_args_1_dash_list[@]}" =~ "$param" ]]; then
      echo "无效的选项 -$param "
      exit 1
    fi
  done
  # 遍历所有以 -- 开头的参数,如果参数不在合法参数列表中,则报错
  for param in "${param_2_dash[@]}"; do
    if ! [[ "${legal_args_2_dash_list[@]}" =~ "$param" ]]; then
      echo "无效的选项 --$param 将被忽略"
      exit 1
    fi
  done

  #然后判断传入的参数的数量,如果只有一个,则判断这个参数是不是以`-`开头,如果不是,则直接return跳过检查函数,说明这个命令是合法的,如`rm /path/to/file`这种命令
  if [[ $# -eq 1 ]]; then
    if ! [[ "${1:0:1}" == "-" ]]; then
      return # 如果不是以`-`开头,则直接return,就是说`rm /path/to/file`这种命令是合法的
    fi
  fi
  # 如果传入的参数的数量大于1,则遍历所有的参数,如果所有参数中,有大于等于两个`-`开头的参数,则直接exit 1报错
  if [[ $# -gt 1 ]]; then
    local count=0
    for arg in "${args[@]}"; do
      if [[ "${arg:0:1}" == "-" ]]; then
        ((count++))
      fi
    done
    if [[ $count -ge 2 ]]; then
      echo "多个选项不能同时使用"
      exit 1
    fi
  fi

  # 下面开始分别讨论每个参数的合法性
  case "$1" in
  # -config --configeration, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-config" | "--configuration")
    if [[ $# -gt 1 ]]; then
      echo "错误:-config --configuration 选项不需要参数"
      exit 1
    fi
    ;;
  # -cron --crontab, 有2种情况,非交互式和交互式,非交互式的情况下,后面必须有一个数字的参数,如果没有,则直接exit 1报错;如果后面没有参数,则直接return跳过检查函数进入交互模式,说明这个命令是合法的,如`rm -cron`这种命令
  "-cron" | "--crontab")
    if [[ $# -eq 1 ]]; then
      # 没有参数,进入交互模式
      echo "进入交互模式"
      return 0
    elif [[ $# -eq 2 && "$2" =~ ^[0-9]+$ ]]; then
      # 有一个数字参数
      echo "检测到数字参数 $2,进入非交互模式"
      return 0
    elif [[ $# -ge 3 ]]; then
      # 参数数量大于等于3
      echo "错误:不支持多余参数"
      return 1
    fi
    ;;
  # -del --delete后面不能有任何参数,如果有,则直接exit 1报错
  "-del" | "--delete")
    if [[ $# -gt 1 ]]; then
      echo "错误:-del --delete 选项不需要参数"
      exit 1
    fi
    ;;
  # -e --empty, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-e" | "--empty")
    if [[ $# -gt 1 ]]; then
      echo "错误:-e --empty 选项不需要参数"
      exit 1
    fi
    ;;
  # -f --force, 后面必须要有一个非`-`开头的参数表示要删除的文件或文件夹,如果没有,则直接exit 1报错
  # 这个在对应的函数中已经判断过了,这里不需要再判断了
  "-f" | "--force") ;;
  # -h --help, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-h" | "--help")
    if [[ $# -gt 1 ]]; then
      echo "错误:-h --help 选项不需要参数"
      exit 1
    fi
    ;;
  # -l -ls --list, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-l" | "-ls" | "--list")
    if [[ $# -gt 1 ]]; then
      echo "错误:-l -ls --list 选项不需要参数"
      exit 1
    fi
    ;;
  # -ra --restore-all, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-ra" | "--restore-all")
    if [[ $# -gt 1 ]]; then
      echo "错误:-ra --restore-all 选项不需要参数"
      exit 1
    fi
    ;;
  # -re --restore, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-re" | "--restore")
    if [[ $# -gt 1 ]]; then
      echo "错误:-re --restore 选项不需要参数"
      exit 1
    fi
    ;;
  # -set --set-path,有2种情况,非交互式和交互式,非交互式的情况下,后面必须只能有一个参数,否则直接exit 1报错;如果后面没有参数,则直接return跳过检查函数进入交互模式,说明这个命令是合法的,如`rm -set`这种命令
  "-set" | "--set-path")
    if [[ $# -eq 1 ]]; then
      # 没有参数,进入交互模式
      return 0
    elif [[ $# -eq 2 ]]; then
      # 有一个参数
      # echo "检测到参数 $2,进入非交互模式"
      return 0
    elif [[ $# -ge 3 ]]; then
      # 参数数量大于等于3
      echo "错误:不支持多余参数"
      return 1
    fi
    ;;
  # -uninstall --uninstall, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-uninstall" | "--uninstall")
    if [[ $# -gt 1 ]]; then
      echo "错误:-uninstall --uninstall 选项不需要参数"
      exit 1
    fi
    ;;
  # -v --version, 后面不能跟参数,如果跟了参数,则直接exit 1报错
  "-v" | "--version")
    if [[ $# -gt 1 ]]; then
      echo "错误:-v --version 选项不需要参数"
      exit 1
    fi
    ;;
  "-update" | "--update")
    if [[ $# -gt 1 ]]; then
      echo "错误:-update --update 选项不需要参数"
      exit 1
    fi
    ;;
  esac

}

# 恢复函数
function restore() {
  function restore_single_file() {
    local file="$1"
    local source_path target_path temp_target_path
    local answer
    if [[ -d "$TRASH_DIR/$file" || -f "$TRASH_DIR/$file" ]]; then
      # 读取对应的info文件,获取源路径和目标路径
      source_path=$(sed -n '2p' "${TRASH_DIR}/${file}/info" | tr -d "'")
      temp_target_path=$(sed -n '1p' "${TRASH_DIR}/${file}/info" | tr -d "'")
      target_path="${temp_target_path}/$(basename "$source_path")"
      real_source_path="$temp_target_path/$(echo "$file" | sed -E 's/.*(文件|文件夹):(.*)\)/\2/')" # 获取真实的源路径
      if [[ -e "$target_path" ]]; then
        # 如果目标路径存在且与源路径不同,则触发覆盖询问
        while true; do
          if [[ "$GLOBAL_ANSWER" == "a" ]]; then
            answer="y"
          elif [[ "$GLOBAL_ANSWER" == "s" ]]; then
            answer="n"
          else
            echo "目标路径已经存在同名文件 \"${real_source_path}\" ,是否覆盖恢复?[y (覆盖)/n (跳过)/q (退出)/a (全部同意)/s (全部跳过)]"
            read answer
          fi

          case $answer in
          y | Y)
            # 如果用户确认覆盖,则将源路径的文件移动到目标路径
            if [[ -f "$source_path" ]]; then
              mv "$source_path" "$temp_target_path"
            elif [[ -d "$source_path" ]]; then
              cp -rf "$source_path" "$temp_target_path"
            fi
            rm -rf "$TRASH_DIR/$file"
            echo "已成功恢复 \"$file\" 到 \"$real_source_path\""
            break
            ;;
          n | N)
            # 如果用户拒绝覆盖,则输出消息并退出循环
            echo "跳过恢复 \"$file\""
            break
            ;;
          q | Q)
            # 如果用户选择退出,则直接退出脚本
            echo "恢复操作已取消"
            exit 0
            ;;
          a | A)
            # 如果用户选择全部同意,则将GLOBAL_ANSWER变量设置为"a",并继续执行后面的步骤
            GLOBAL_ANSWER="a"
            ;;
          s | S)
            # 如果用户选择全部跳过,则将GLOBAL_ANSWER变量设置为"s",并直接返回
            GLOBAL_ANSWER="s"
            break
            ;;
          *)
            # 如果用户输入了非法的选项,则提示用户重新输入
            echo "请输入有效的选项![y (覆盖)/n (跳过)/q (退出)/a (全部同意)/s (全部跳过)]"
            ;;
          esac
        done
      else
        # 如果目标路径不存在或者与源路径相同,则直接移动源路径的文件到目标路径
        if [[ -f "$source_path" ]]; then
          mv "$source_path" "$temp_target_path"
        elif [[ -d "$source_path" ]]; then
          cp -rf "$source_path" "$temp_target_path"
        fi
        rm -rf "$TRASH_DIR/$file"
        echo "已成功恢复 \"$file\" 到 \"$real_source_path\""
      fi
    else
      echo "$file 不是一个有效的文件或目录1"
    fi
  }
  # restore主函数:
  # 判断垃圾桶目录是否为空
  if [ "$(ls -A ${TRASH_DIR})" ]; then    # 如果不为空
    declare -g is_restore_all=0           # 默认不是恢复所有文件
    if [[ "$is_restore_all" == 1 ]]; then # 判断 is_restore_all 是否为真,如果为True的话,说明是-ra,需要恢复所有文件
      # 获取所有被删除的文件列表
      files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
      # 遍历所有文件进行恢复
      for num in $(seq 1 "$(echo "$files" | wc -l)"); do
        # 获取当前文件对应的文件名
        file_name=$(echo "${files}" | sed -n "${num}p" | awk -F '"' '{print $2}')
        restore_single_file "$file_name"
      done
    elif [[ "$is_restore_all" == 0 ]]; then # # 否则 is_restore_all 为假,说明是-re,需要恢复指定文件
      # 列出所有被删除的文件,并且文件开头需要有编号1-n,然后用户输入要恢复的文件编号
      echo -e "以下是垃圾桶目录中的所有文件:\n"
      # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
      files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
      echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"
      read -p "请输入要恢复的文件编号(多个文件用空格隔开):" res_files
      # 判断用户输入的文件编号是否合法
      if [[ "$res_files" =~ ^[0-9]+(\ [0-9]+)*$ ]]; then
        for res_file in $(echo "${res_files}"); do
          # 将用户输入的文件编号转换为文件名
          file_name=$(echo "${files}" | sed -n "${res_file}p" | awk -F '"' '{print $2}')
          restore_single_file "$file_name"
        done
      fi
    else
      echo "参数错误:文件编号只能是数字"
    fi
  else
    echo "垃圾桶目录为空"
  fi
}

# 执行脚本
no_macOS                                  # 检查是否是macOS系统
CURRENT_TIME=$(date "+%Y-%m-%d-%H:%M:%S") # 定义 CURRENT_TIME 变量
declare -g is_add_alias=0                 # 设置一个全局变量来表示是否添加过别名
init_new_rm                               # 初始化新的rm命令
self_eraser                               # 自我删除
parse_args "$@"                           # 解析参数是否合法
GLOBAL_ANSWER=""                          # 全局变量,用于存储用户对于确认操作的选择

# 处理命令行参数
case "$1" in
-h | --help) # 帮助信息
  usage
  exit 0
  ;;
-re | --restore) # 恢复文件
  restore
  ;;
-f | --force | -rf) # 强制删除
  check_before_rm "$@"
  # 去掉命令行参数中的 -f 参数,并执行真正的删除操作
  shift
  # 定义一个数组,用于存储不存在的文件
  not_exist_files=()

  for file in "$@"; do
    if [[ ! -e "$file" ]]; then
      # 将不存在的文件添加到数组中
      not_exist_files+=("$file")
      echo "错误:文件或目录 '$file' 不存在"
    fi
  done

  # 如果存在不存在的文件则输出数组,并退出程序
  if [[ ${#not_exist_files[@]} -gt 0 ]]; then
    echo "请再次检查对应的文件或目录是否存在!"
    exit 1
  fi

  if [[ "$#" -gt 0 ]]; then
    read -p "确定 永久删除 这些文件? $(echo "$@" | sed 's/\(-f\|--force\|-rf\) //')。注意:删除后将无法恢复! [y/N]: " answer

    if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
      rm -rf "$@"
      echo "已成功永久删除指定的文件或目录"
    else
      echo "删除操作已取消"
      exit 1
    fi
  else
    echo "没有指定要删除的文件或目录"
  fi
  ;;
-l | -ls | --list) # 列出垃圾桶目录中的所有文件
  # 判断垃圾桶目录是否为空
  if [ "$(ls -A ${TRASH_DIR})" ]; then
    # 如果不为空,列出所有被删除的文件,并且文件开头需要有编号1-n
    echo -e "以下是垃圾桶目录中的所有文件:\n"
    # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
    files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
    echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"
  else
    echo "垃圾桶目录为空"
  fi
  ;;
-e | --empty) # 清空垃圾桶
  # 清空垃圾桶目录
  # echo "垃圾桶中所有文件:"
  # echo -e '大小    "删除时间 (文件类型:文件名)"\n'
  # du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||"| sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
  echo -e "以下是垃圾桶目录中的所有文件:\n"
  # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
  files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
  echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"
  read -p "确定 清空 垃圾桶? [y/N]: " answer
  if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
    rm -rf "${TRASH_DIR:?}/"*
    echo "已成功 清空 垃圾桶:${TRASH_DIR}"
  else
    echo "清空 垃圾桶操作已取消"
    exit 1
  fi
  ;;
-config | --configeration) # 查看垃圾桶配置
  echo "new_rm: v${VERSION}"
  echo "垃圾桶目录:${TRASH_DIR}"
  echo "垃圾桶自动清空的时间:$(crontab -l | grep -Eo "^# 每隔[0-9]+小时自动清理垃圾桶,当前垃圾桶路径:\".*?\"" | sed -E 's/^# 每隔([0-9]+)小时自动清理垃圾桶,当前垃圾桶路径:".*"$/\1/g') 小时"
  ;;
-update | --update) # 更新脚本
  wget -O remove.sh https://git.dreamdusk.com/Dean/new_rm/raw/branch/main/new_rm.sh && chmod +x remove.sh && ./remove.sh
  ;;
-set | --set-path) # 设置垃圾桶目录
  # 如果 -set 后面有参数,则进入非交互模式
  if [[ -n "$2" ]]; then

    # 如果原垃圾桶非空,则提示用户是否将原垃圾桶中的所有文件移动到新的垃圾桶目录
    if [ "$(ls -A ${TRASH_DIR})" ] && [ "$TRASH_DIR" != "$2" ]; then
      echo "警告:您正在将当前的垃圾桶目录 $TRASH_DIR 更改为 $2,原垃圾桶已包含以下文件:"
      echo -e '大小    "删除时间 (文件类型:文件名)"\n'
      du -sh "${TRASH_DIR}"/* | sed "s|${TRASH_DIR}/||" | sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
      echo -e "\n确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗?\n如果选择 [y],则会将原垃圾桶中的所有文件移动到新的垃圾桶目录\n如果选择 [n],则会在不移动原垃圾桶中的文件的情况下,直接将垃圾桶目录更改为新的垃圾桶目录\n如果选择 [c],则会取消更改垃圾桶目录的操作"
      read -p "请输入 [y/N/c\]: " answer
      if [[ "$answer" == "y" || "$answer" == "Y" ]]; then # 移动原垃圾桶中的所有文件到新的垃圾桶目录,然后将垃圾桶目录更改为新的垃圾桶目录

        if [ ! -d "$2" ]; then
          echo "警告:新垃圾桶目录 $2 不存在,将自动创建"
          mkdir -p "$2"
        fi

        mv "${TRASH_DIR:?}/"* "$2/"
        echo "已成功 迁移 原垃圾桶中的所有文件到:$2"

        # 删除旧的垃圾桶目录
        rm -r "${TRASH_DIR:?}"
        echo "已删除旧的垃圾桶目录:$TRASH_DIR"

        update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录

      elif [[ "$answer" == "n" || "$answer" == "N" ]]; then # 不移动原垃圾桶中的所有文件到新的垃圾桶目录,然后将垃圾桶目录更改为新的垃圾桶目录
        # 显示取消消息
        echo "迁移操作已取消"

        # 不移动原垃圾桶中的所有文件到新的垃圾桶目录
        echo "未移动任何文件到新的垃圾桶目录"

        if [ ! -d "$2" ]; then
          echo "警告:垃圾桶目录 $2 不存在,将自动创建"
          mkdir -p "$2"
        fi

        update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录

        # 提醒用户新的垃圾桶目录已经设置过,并且原垃圾桶及其中的垃圾还在,请及时清理
        echo "警告:虽然您已经设置了新的垃圾桶目录 $2,但是原垃圾桶 $TRASH_DIR 和其中的垃圾还在,请及时清理。"
      elif [[ "$answer" == "c" || "$answer" == "C" ]]; then # 取消更改垃圾桶目录的操作
        echo "设置操作已取消"
        exit 0
      else
        echo "输入不合法,已取消操作"
        exit 1
      fi
    # 如果原垃圾桶为空,则直接更新垃圾桶路径
    else
      # 如果新垃圾桶目录不存在,则自动创建
      if [ ! -d "$2" ]; then
        echo "警告:垃圾桶目录 $2 不存在,将自动创建"
        mkdir -p "$2"
      fi

      update_TRASH_DIR_in_function_set_path "$2" # 更新函数中的垃圾桶目录

    fi
  # 如果没有指定 -set 参数,则进入交互式设置垃圾桶路径
  else
    # 交互式设置垃圾桶路径

    # 先保存当前垃圾桶的路径
    CURRENT_TRASH_DIR=$TRASH_DIR

    # 再告知用户当前垃圾桶的路径
    echo "当前垃圾桶路径为:$CURRENT_TRASH_DIR"

    read -p "请输入新的垃圾桶路径:" TRASH_DIR

    # 如果原垃圾桶非空,则提示用户是否将原垃圾桶中的所有文件移动到新的垃圾桶目录
    if [[ -d "${CURRENT_TRASH_DIR}" ]] && [ "$(ls -A ${CURRENT_TRASH_DIR})" ] && [ "$CURRENT_TRASH_DIR" != "$TRASH_DIR" ]; then
      echo "警告:您正在将当前的垃圾桶目录 $CURRENT_TRASH_DIR 更改为 $TRASH_DIR,该原垃圾桶已包含以下文件:"
      echo -e '大小    "删除时间 (文件类型:文件名)"\n'
      du -sh "${CURRENT_TRASH_DIR}"/* | sed "s|${CURRENT_TRASH_DIR}/||" | sed 's/\(.*\)\t\(.*\)/\1\t\"\2\"/'
      # read -p "确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗? [y/N/c]: " answer
      echo -e "\n确定要将原垃圾桶中的所有文件移动到新的垃圾桶目录吗?\n如果选择 [y],则会将原垃圾桶中的所有文件移动到新的垃圾桶目录\n如果选择 [n],则会在不移动原垃圾桶中的文件的情况下,直接将垃圾桶目录更改为新的垃圾桶目录\n如果选择 [c],则会取消更改垃圾桶目录的操作"
      read -p "请输入 [y/N/c\]: " answer
      if [[ "$answer" == "y" || "$answer" == "Y" ]]; then

        # 如果垃圾桶目录不存在,则创建
        if [ ! -d "$TRASH_DIR" ]; then
          echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
          mkdir -p "$TRASH_DIR"
        fi

        # 移动原垃圾桶中的所有文件到新的垃圾桶目录
        mv "${CURRENT_TRASH_DIR:?}/"* "$TRASH_DIR/"
        echo "已成功 迁移 原垃圾桶中的所有文件到:$TRASH_DIR"

        # 删除旧的垃圾桶目录
        rm -r "${CURRENT_TRASH_DIR:?}"
        echo "已删除旧的垃圾桶目录:$CURRENT_TRASH_DIR"

        update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录

      elif [[ "$answer" == "n" || "$answer" == "N" ]]; then
        # 显示取消消息
        echo "迁移操作已取消"

        # 不移动原垃圾桶中的所有文件到新的垃圾桶目录
        echo "未移动任何文件到新的垃圾桶目录"

        # 如果垃圾桶目录不存在,则创建
        if [ ! -d "$TRASH_DIR" ]; then
          echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
          mkdir -p "$TRASH_DIR"
        fi

        update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录

        # 提醒用户新的垃圾桶目录已经设置过,并且原垃圾桶及其中的垃圾还在,请及时清理
        echo "警告:虽然您已经设置了新的垃圾桶目录 $TRASH_DIR,但是原垃圾桶 $CURRENT_TRASH_DIR 和其中的垃圾还在,请及时清理。"
      elif [[ "$answer" == "c" || "$answer" == "C" ]]; then
        echo "设置操作已取消"
        exit 0
      else
        echo "输入不合法,已取消操作"
        exit 1
      fi
    # 如果原垃圾桶为空,则直接更新垃圾桶路径
    else
      # 如果垃圾桶目录不存在,则创建
      if [ ! -d "$TRASH_DIR" ]; then
        echo "警告:垃圾桶目录 $TRASH_DIR 不存在,将自动创建"
        mkdir -p "$TRASH_DIR"
      fi

      update_TRASH_DIR_in_function_set_path "$TRASH_DIR" # 更新函数set_path中的垃圾桶目录

    fi

  fi
  ;;
-cron | --crontab) # 设置定时清理垃圾桶的时间间隔
  # 如果用户输入的参数不是 `-cron` 或 `-cron x`(x 为 1~23 之间的正整数),则报错
  if [[ "$#" -ne 1 && "$#" -ne 2 ]] || [[ "$#" -eq 1 && ("$1" != "-cron") ]] || [[ "$#" -eq 2 && ("$1" != "-cron" || ! "$2" =~ ^([1-9]|1[0-9]|2[0-3])$) ]]; then
    echo "参数错误:参数只能是 -cron 或 -cron x(x 为 1~23 之间的正整数)"
    exit 1
  fi
  # 如果 -cron 后面是 1~23 之间的正整数,则进入非交互式
  if [[ "$2" =~ ^(1[0-9]|[1-9]|2[0-3])$ ]]; then
    interval="$2"
    # 如果 -cron 后面没有数字参数,则进入交互式
  else
    read -p "请输入一个时间间隔(以小时为单位):" interval
    # 如果用户输入的不是 1~23 之间的正整数,则让用户重新输入
    while [[ ! "$interval" =~ ^[0-9]+$ ]] || [[ "$interval" -eq 0 ]]; do
      echo "参数错误:时间间隔取值范围是1~23时间的整数"
      read -p "请输入一个时间间隔(以小时为单位):" interval
    done
  fi

  # 获取原来 crontab 中自动清空回收站的时间间隔
  current_interval=$(crontab -l | grep -m 1 -w "$command" | awk -F '[^0-9]*' '{print $2}')

  # echo "原来 $current_interval 小时清空一次回收站"

  # 将时间间隔转换为 crontab 表达式
  if [[ "$interval" =~ ^(1[0-9]|[1-9]|2[0-3])$ ]]; then
    expression=$(echo "0 */$interval * * *") # 每隔指定小时执行一次

    # 定义任务命令和注释行
    comment="# 每隔${interval}小时自动清理垃圾桶,当前垃圾桶路径:\"${TRASH_DIR:?}\",请勿随意修改本行及下一行的内容!一个标点符号都不能动!!否则rm_new会出问题!"
    command="[ -n \"\$(ls -A ${TRASH_DIR:?})\" ] && rm -rf ${TRASH_DIR:?}/* || true"

    # 添加或更新任务并添加注释行和提示信息
    pattern="$command"
    task="$expression $command"
    # 如果 crontab 中已经存在自动清理任务,则更新任务
    if crontab -l | grep -q "$pattern"; then
      (crontab -l | sed "s|$pattern.*|$comment\n$task|; /^$/d; \$a\\") | crontab -
      echo -e "成功修改自动清理任务的时间:\n已由 $current_interval 小时清空一次回收站 修改为 ${interval} 小时清空一次回收站 ${TRASH_DIR:?}"
    # 如果 crontab 中不存在自动清理任务,则添加任务
    else
      (echo -e "$comment\n$task") | crontab -
      echo -e "成功修改自动清理任务的时间:\n已由 $current_interval 小时清空一次回收站 修改为 ${interval} 小时清空一次回收站 ${TRASH_DIR:?}"
    fi

  else
    echo "错误:必须提供一个整数时间间隔(以小时为单位)"
  fi
  ;;

# elif [[ "$1" == "-del" ]] || [[ "$1" == "--delete" ]]; then
-del | --delete)
  # 判断垃圾桶目录是否为空
  if [ "$(ls -A ${TRASH_DIR})" ]; then
    # 如果不为空,列出所有被删除的文件,并且文件开头需要有编号1-n,然后用户输入要删除的文件编号
    echo -e "以下是垃圾桶目录中的所有文件:\n"
    # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
    files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
    echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"
    read -p "请输入要永久删除的文件编号(多个文件用空格隔开):" del_files
    # 判断用户输入的文件编号是否合法
    if [[ "$del_files" =~ ^[0-9]+(\ [0-9]+)*$ ]]; then
      for del_file in $(echo "${del_files}"); do
        # 将用户输入的文件编号转换为文件名
        file_name=$(echo "${files}" | sed -n "${del_file}p" | awk -F '"' '{print $2}')
        # 删除用户输入的文件
        rm -rf "${TRASH_DIR}/${file_name}"
        echo "已永久删除文件:\"${file_name}\""
      done
    else
      echo "参数错误:文件编号只能是数字"
    fi
  else
    echo "垃圾桶目录为空"
  fi
  ;;
-uninstall | --uninstall) # 卸载rm_new
  uninstall_new_rm
  ;;
# # 处理 "-ra" 参数
# elif [[ "$1" == "-ra" ]] || [[ "$1" == "--restore-all" ]]; then
-ra | --restore-all) # 恢复垃圾桶中的所有文件
  # 判断垃圾桶目录是否为空
  if [ "$(ls -A ${TRASH_DIR})" ]; then
    # 如果不为空,列出所有被删除的文件
    echo -e "以下是垃圾桶目录中的所有文件:\n"
    # files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ printf "%-10s%-10s\"%s\"\n", NR, $1, substr($0, index($0, $2)) }')
    files=$(cd "${TRASH_DIR}" && du -sh * | sort -k2,2h | awk '{ size=$1"B"; path=substr($0, index($0, $2)); printf "%-10s%-10s\"%s\"\n", NR, size, path }')
    echo -e "编号      大小      \"删除时间 (文件类型:文件名)\"\n${files}"

    # 询问用户是否确认恢复所有文件
    read -p "确定 恢复 垃圾桶目录中的所有文件?[y/N]" answer
    if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
      for file in "${TRASH_DIR}"/*; do
        # 设置一个全局变量,标志告诉restore函数是否是恢复所有文件
        declare -g is_restore_all=1
        restore "$(basename "$file")"
      done
    else
      declare -g is_restore_all=0
      echo "恢复操作已取消"
      exit 1
    fi
  else
    # 如果为空,输出提示信息并退出程序
    echo "垃圾桶目录为空,无法恢复任何文件"
    exit 1
  fi
  ;;
# 处理 "-v" "--version" 参数
-v | --version)
  echo "new_rm: v${VERSION}"
  ;;
*) # 如果以上参数都不匹配,就说明用户需要的是直接删除文件

  #更新脚本的时候就不用报错"rm: 缺少参数"了,检测方法是:1.如果is_add_alias=0,说明脚本没有修改过rc_file添加别名;2.如果当前脚本是"/usr/local/bin/new_rm";结合 1&&2==true 就能判断出不是更新脚本
  if [[ "$0" = "/usr/local/bin/new_rm" ]] && [[ "$is_add_alias" -eq 0 ]]; then
    # 如果没有任何参数,则直接exit 1报错
    if [[ $# -eq 0 ]]; then
      echo "rm: 缺少参数"
      echo "请尝试使用 'rm --help' 以获取更多信息。"
    fi
  fi

  # 调用 check_before_rm 检查
  check_before_rm "$@"
  # 遍历所有命令行参数
  for arg in "$@"; do
    # 判断当前参数是否为一个存在的文件(夹)
    if [[ -e "$arg" ]]; then
      # 获取要删除的文件或目录的绝对路径和目录路径
      file_path="$(readlink -f "$arg")"
      dir_path="$(dirname "$file_path")"

      # 获取文件或目录名
      if [[ -d $file_path ]]; then
        file_type="文件夹"
      else
        file_type="文件"
      fi
      file_name="$(basename "$file_path")"

      # 在垃圾桶目录中创建与文件名相同的子目录,并将文件或目录移动到该子目录下
      mkdir -p "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})"
      mv "$file_path" "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/${file_name}"

      # 将文件的信息写入到info文件中
      echo "$dir_path" >"${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/info"
      echo "${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/${file_name}" >>"${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})/info"

      if [[ "$?" -eq 0 ]]; then
        echo "已将 $file_name 移动到垃圾桶目录:${TRASH_DIR}/${CURRENT_TIME} (${file_type}:${file_name})"
      else
        # 如果移动文件时出现错误,则输出错误消息,并尝试恢复删除的文件
        echo "移动 $file_name 到垃圾桶目录时出现错误"
        last_file="${CURRENT_TIME} (${file_type}:${file_name})"
        mv "${TRASH_DIR}/${last_file}/${file_name}" "$dir_path/"
        rm -rf "${TRASH_DIR}/${last_file}"
        echo "尝试恢复 $file_name"
      fi
    else
      echo "$arg 不是一个有效的文件或目录2"
    fi
  done
  ;;
esac

# echo "is_add_alias=$is_add_alias"
# echo "shell=$shell"
# echo "rc_file=$rc_file"

#source需要在最后执行,如果is_add_alias=1,说明脚本重新修改过rc_file添加别名,那么就需要重新运行zsh或者bash
if [[ "$is_add_alias" -eq 1 ]]; then
  $shell
fi
Last modification:August 28, 2024
V50%看看实力