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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/992737/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 20:58:12  来源:igfitidea点击:

Safe rm -rf function in shell script

regexbashsed

提问by Max

This question is similar to What is the safest way to empty a directory in *nix?

这个问题类似于在 *nix 中清空目录的最安全方法什么?

I'm writing bash script which defines several path constants and will use them for file and directory manipulation (copying, renaming and deleting). Often it will be necessary to do something like:

我正在编写 bash 脚本,它定义了几个路径常量,并将它们用于文件和目录操作(复制、重命名和删除)。通常需要执行以下操作:

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

While developing this script I'd want to protect myself from mistyping names like PATH1 and PATH2 and avoid situations where they are expanded to empty string, thus resulting in wiping whole disk. I decided to create special wrapper:

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

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

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

Which will be called as:

这将被称为:

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

Regex (or sed expression) should catch paths like "*", "/*", "/**/", "///*" etc. but allow paths like "dir", "/dir", "/dir1/dir2/", "/dir1/dir2/*". Also I don't know how to enable shell globbing in case like "/dir with space/*". Any ideas?

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

EDIT: this is what I came up with so far:

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

rmrf() {
    local RES
    local RMPATH=""
    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
}

Intended use is (note an asterisk insidequotes):

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

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

where $SOMEPATH is not system or /home directory (in my case all such operations are performed on filesystem mounted under /scratch directory).

其中 $SOMEPATH 不是系统或 /home 目录(在我的情况下,所有此类操作都是在 /scratch 目录下安装的文件系统上执行的)。

CAVEATS:

警告:

  • not tested very well
  • not intended to use with paths possibly containing '..' or '.'
  • should not be used with user-supplied paths
  • rm -rf with asterisk probably can fail if there are too many files or directories inside $SOMEPATH (because of limited command line length) - this can be fixed with 'for' loop or 'find' command
  • 没有很好地测试
  • 不打算与可能包含“..”或“.”的路径一起使用
  • 不应与用户提供的路径一起使用
  • 如果 $SOMEPATH 中的文件或目录过多(因为命令行长度有限),带有星号的 rm -rf 可能会失败 - 这可以使用“for”循环或“find”命令修复

回答by SpliFF

I've found a big danger with rm in bash is that bash usually doesn't stop for errors. That means that:

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

cd $SOMEPATH
rm -rf *

Is a very dangerous combination if the change directory fails. A safer way would be:

如果更改目录失败,这是一个非常危险的组合。更安全的方法是:

cd $SOMEPATH && rm -rf *

Which will ensure the rf won't run unless you are really in $SOMEPATH. This doesn't protect you from a bad $SOMEPATH but it can be combined with the advice given by others to help make your script safer.

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

EDIT: @placeybordeaux makes a good point that if $SOMEPATH is undefined or empty cddoesn't treat it as an error and returns 0. In light of that this answer should be considered unsafe unless $SOMEPATH is validated as existing and non-empty first. I believe cdwith no args should be an illegal command since at best is performs a no-op and at worse it can lead to unexpected behaviour but it is what it is.

编辑:@placeybordeaux 提出了一个很好的观点,如果 $SOMEPATH 未定义或为空cd,则不会将其视为错误并返回 0。鉴于此,除非首先将 $SOMEPATH 验证为存在且非空,否则应认为此答案不安全. 我相信cd没有 args 应该是一个非法命令,因为充其量是执行无操作,更糟糕的是它可能导致意外行为,但事实就是如此。

回答by dhill

There is a set -ubash directive that will cause exit, when uninitialized variable is used. I read about it here, with rm -rfas an example. I think that's what you're looking for. And here is set's manual.

set -u当使用未初始化的变量时,有一个bash 指令会导致退出。我在这里阅读了它,rm -rf作为一个例子。我想这就是你要找的。这里是set 的手册

回答by dhill

I think "rm" command has a parameter to avoid the deleting of "/". Check it out.

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

回答by ynimous

I would recomend to use realpath(1) and not the command argument directly, so that you can avoid things like /A/B/../ or symbolic links.

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

回答by Jonathan Leffler

Generally, when I'm developing a command with operations such as 'rm -fr' in it, I will neutralize the remove during development. One way of doing that is:

通常,当我开发带有诸如“ rm -fr”之类的操作的命令时,我会在开发过程中中和删除。一种方法是:

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

This shows me what should be deleted - but does not delete it. I will do a manual clean up while things are under development - it is a small price to pay for not running the risk of screwing up everything.

这向我展示了应该删除的内容 - 但不会删除它。我将在开发过程中进行手动清理 - 这是一个很小的代价,可以让您不冒搞砸一切的风险。

The notation '"/${PATH1}"' is a little unusual; normally, you would ensure that PATH1 simply contains an absolute pathname.

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

Using the metacharacter with '"${PATH2}/"*' is unwise and unnecessary. The only difference between using that and using just '"${PATH2}"' is that if the directory specified by PATH2 contains any files or directories with names starting with dot, then those files or directories will not be removed. Such a design is unlikely and is rather fragile. It would be much simpler just to pass PATH2 and let the recursive remove do its job. Adding the trailing slash is not necessarily a bad idea; the system would have to ensure that $PATH2contains a directory name, not just a file name, but the extra protection is rather minimal.

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

Using globbing with 'rm -fr' is usually a bad idea. You want to be precise and restrictive and limiting in what it does - to prevent accidents. Of course, you'd never run the command (shell script you are developing) as root while it is under development - that would be suicidal. Or, if root privileges are absolutely necessary, you neutralize the remove operation until you are confident it is bullet-proof.

使用 ' rm -fr'通配符通常是一个坏主意。您希望对其功能进行精确、限制和限制 - 以防止发生事故。当然,在开发过程中,您永远不会以 root 身份运行命令(您正在开发的 shell 脚本)——那是自取其辱。或者,如果绝对需要 root 权限,您可以取消删除操作,直到您确信它是防弹的。

回答by Max

Meanwhile I've found this perl project: http://code.google.com/p/safe-rm/

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

回答by too much php

If it is possible, you should try and put everything into a folder with a hard-coded name which is unlikely to be found anywhere else on the filesystem, such as 'foofolder'. Then you can write your rmrf()function as:

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

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

There is no way that function can delete anything but the files you want it to.

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

回答by too much php

You may use

您可以使用

set -f    # cf. help set 

to disable filename generation (*).

禁用文件名生成 (*)。

回答by Howard Hong

You don't need to use regular expressions.
Just assign the directories you want to protect to a variable and then iterate over the variable. eg:

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

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

回答by Panfeng Li

Add the following codes to your ~/.bashrc

将以下代码添加到您的 ~/.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'

Every time you need to rmsomething, first consider del, you can change the trash folder. If you do need to rmsomething, you could go to the trash folder and use rmi.

每次您需要rm某事时,首先考虑del,您可以更改垃圾文件夹。如果您确实需要rm某些东西,您可以转到垃圾文件夹并使用rmi.

One small bug for delis that when dela folder, for example, my_folder, it should be del my_folderbut not del my_folder/since in order for possible later restore, I attach the time information in the end ("$@_$now"). For files, it works fine.

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