shell脚本中的安全rm -rf函数

时间:2009-06-14 12:31:56

标签: regex bash sed

此问题与What is the safest way to empty a directory in *nix?

类似

我正在编写bash脚本,它定义了几个路径常量,并将它们用于文件和目录操作(复制,重命名和删除)。通常有必要做类似的事情:

rm -rf "/${PATH1}"
rm -rf "${PATH2}/"*

在开发这个脚本的过程中,我想保护自己免受类似PATH1和PATH2等名称的错误输入,并避免将它们扩展为空字符串,从而导致擦除整个磁盘。我决定创建特殊的包装器:

rmrf() {
    if [[ $1 =~ "regex" ]]; then
        echo "Ignoring possibly unsafe path ${1}"
        exit 1
    fi

    shopt -s dotglob
    rm -rf -- $1
    shopt -u dotglob
}

将被称为:

rmrf "/${PATH1}"
rmrf "${PATH2}/"*

正则表达式(或sed表达式)应该捕获“*”,“/ *”,“/ ** /”,“/// *”等路径,但允许路径如“dir”,“/ dir”, “/ dir1 / dir2 /”,“/ dir1 / dir2 / *”。另外我不知道如何在“/ dir with space / *”的情况下启用shell globbing。有什么想法吗?

编辑:这是我到目前为止所提出的:

rmrf() {
    local RES
    local RMPATH="${1}"
    SAFE=$(echo "${RMPATH}" | sed -r 's:^((\.?\*+/+)+.*|(/+\.?\*+)+.*|[\.\*/]+|.*/\.\*+)$::g')
    if [ -z "${SAFE}" ]; then
        echo "ERROR! Unsafe deletion of ${RMPATH}"
        return 1
    fi

    shopt -s dotglob
    if [ '*' == "${RMPATH: -1}" ]; then
        echo rm -rf -- "${RMPATH/%\*/}"*
        RES=$?
    else
        echo rm -rf -- "${RMPATH}"
        RES=$?
    fi
    shopt -u dotglob

    return $RES
}

预期用途是(注意星号里面引号):

rmrf "${SOMEPATH}"
rmrf "${SOMEPATH}/*"

其中$ SOMEPATH不是system或/ home目录(在我的例子中,所有这些操作都是在/ scratch目录下挂载的文件系统上执行的。)

注意事项:

  • 未经过良好测试
  • 不打算用于可能包含'..'或'。'
  • 的路径
  • 不应与用户提供的路径一起使用
  • 如果$ SOMEPATH中有太多文件或目录(因为命令行长度有限),带有星号的
  • rm -rf可能会失败 - 这可以通过'for'循环或'find'命令修复

10 个答案:

答案 0 :(得分:7)

我发现bash中的rm存在很大危险,因为bash通常不会因错误而停止。这意味着:

cd $SOMEPATH
rm -rf *

如果更改目录失败,则是非常危险的组合。更安全的方式是:

cd $SOMEPATH && rm -rf *

除非你真的在$ SOMEPATH中,否则这将确保rf不会运行。这并不能保护您免受错误的$ SOMEPATH的影响,但它可以与其他人提供的建议相结合,以帮助您使脚本更安全。

编辑:@placeybordeaux指出,如果$ SOMEPATH未定义或为空cd不将其视为错误并返回0.鉴于此答案应视为不安全,除非$ SOMEPATH是首先验证为现有和非空。我认为没有args的cd应该是一个非法的命令,因为最好是执行无操作,更糟糕的是它可能导致意外的行为,但它就是它。

答案 1 :(得分:4)

当使用未初始化的变量时,有一个set -u bash指令将导致退出。我阅读了here,以rm -rf为例。我认为这就是你要找的东西。这是set's manual

答案 2 :(得分:2)

我认为“rm”命令有一个参数可以避免删除“/”。看看吧。

答案 3 :(得分:2)

我建议直接使用realpath(1)而不是命令参数,这样就可以避免使用/A/B/../或符号链接。

答案 4 :(得分:2)

通常,当我在开发一个包含“rm -fr”等操作的命令时,我会在开发期间中和删除。一种方法是:

RMRF="echo rm -rf"
...
$RMRF "/${PATH1}"

这显示我应删除的内容 - 但不删除它。当事情正在开发中时,我会做一个手动清理工作 - 这是一个很小的代价,因为它不会冒险搞砸一切。

符号“"/${PATH1}"”有点不寻常;通常,您将确保PATH1只包含绝对路径名。

将元字符与“"${PATH2}/"*”一起使用是不明智和不必要的。使用它和仅使用“"${PATH2}"”之间的唯一区别是,如果PATH2指定的目录包含名称以dot开头的任何文件或目录,则不会删除这些文件或目录。这种设计不太可能,而且相当脆弱。传递PATH2并让递归删除完成它的工作会简单得多。添加尾部斜杠不一定是个坏主意;系统必须确保$PATH2包含目录名,而不仅仅是文件名,但额外​​的保护是相当小的。

对'rm -fr'使用globbing通常是一个坏主意。你想要精确和限制,并限制它的作用 - 以防止事故。当然,你永远不会在开发过程中以root身份运行命令(你正在开发的shell脚本) - 这将是自杀性的。或者,如果绝对需要root权限,则可以中和删除操作,直到您确信它是防弹为止。

答案 5 :(得分:1)

与此同时,我发现了这个perl项目:http://code.google.com/p/safe-rm/

答案 6 :(得分:1)

如果可能,您应该尝试将所有内容放入具有硬编码名称的文件夹中,该文件夹不太可能在文件系统的任何其他位置找到,例如“foofolder”。然后,您可以将rmrf()函数编写为:

rmrf() {
    rm -rf "foofolder/$PATH1"
    # or
    rm -rf "$PATH1/foofolder"
}

除了你想要的文件外,该函数无法删除任何内容。

答案 7 :(得分:1)

您可以使用

set -f    # cf. help set 

禁用文件名生成(*)。

答案 8 :(得分:1)

您不需要使用正则表达式 只需将要保护的目录分配给变量,然后迭代变量即可。例如:

protected_dirs="/ /bin /usr/bin /home $HOME"
for d in $protected_dirs; do
    if [ "$1" = "$d" ]; then
        rm=0
        break;
    fi
done
if [ ${rm:-1} -eq 1 ]; then
    rm -rf $1
fi

答案 9 :(得分:0)

将以下代码添加到~/.bashrc

# safe delete
move_to_trash () { now="$(date +%Y%m%d_%H%M%S)"; mv "$@" ~/.local/share/Trash/files/"$@_$now"; }
alias del='move_to_trash'

# safe rm
alias rmi='rm -i'

每当您需要rm某事时,首先考虑del,您可以更改垃圾文件夹。如果您确实需要rm某些内容,则可以转到垃圾箱文件夹并使用rmi

del的一个小错误是当del文件夹(例如my_folder)时,它应该是del my_folder而不是del my_folder/,因为顺序为了可能以后恢复,我最后附上时间信息("$@_$now")。对于文件,它工作正常。