By Weijun Zhou


2019-03-09 02:32:08 8 Comments

Note: If you googled this by the title of this question, don't use this script unless you know what it is supposed to do.

This is a script in bash 3+ that I have used for long for preventing rm * and rm -rf * from accidentally invoked and removing important files by mistake. I put it in my ~/.bash_aliases.

alias rm='set -f;rm'
rm(){
    if [[ "$-" == *i* ]]
    then
        if [ "$1" = "*" ] || [ "$2" = "*" ] || [ "$1" = "./*" ] || [ "$2" = "./*" ]
        then
            echo "Abort: refusing to remove *, please go to the parent folder and do rm <folder_name>/*" 1>&2
            set +f
            return 1
        fi
    fi  
    set +f
    /bin/rm -i [email protected]
}

set +f

I would like to know whether there are any vulnerabilities and whether it can be improved.

5 comments

@Toby Speight 2019-03-12 10:25:00

Restore options properly

Instead of set -f in our alias and unconditional set +f in the function, we can save the current shell options using set +o to print them as commands:

fun() {
    restore=$(set +o)

    set -f        
    # ... and any other option changes
    # and perform the task

    # finally (instead of set +f)
    eval "$restore"
}

@Weijun Zhou 2019-03-12 01:03:51

This is the version I am using after taking the advance of above answers. Only the core if is written.

if [[ $- == *i* ]]
then
    set +f
    for arg in "[email protected]"
    do # We want to abort the whole command even if only some of the arguments contain dangerous patterns
        if [[ "$arg" == "*" || "$arg" == "./*" ]] # Can be replaced by a custom program written in Python etc. to detect dangerous patterns
        then
            #abort
            return 1
        fi
    done
    declare -a all
    for arg in "[email protected]"
    do #We need to collect all arguments instead of `rm`-ing one by one otherwise `rm -rf file` does not work properly
        if [[ "$arg" == *[*?]* ]]
        then
            expanded=($arg)
            all+=("${expanded[@]}")
        else
            all+=("$arg")                                                                                                                                                                               
        fi
    done
    command rm -i "${all[@]}"
...

@Toby Speight 2019-03-11 10:17:29

Don't call your function rm

If you start depending on this safety net, you'll eventually have an accident on a system with a standard rm (e.g. when you become root and find yourself using dash for admin tasks).

I'd suggest

weijun_rm() {
    # your safer implementation
}

rm() {
     echo "Disabled - please use weijun_rm instead" >&2
     return 1
}

This will train you not to use rm for deleting files. When your function isn't available and you have to use the real rm, you will be on alert and extra careful to check the arguments.

@Weijun Zhou 2019-03-11 14:06:18

Thank you for your suggestion. This is just an extra protection. I don't depend on this and I know what I am doing in most cases. Just in case I didn't get enough sleep and do sth silly ...

@Toby Speight 2019-03-11 14:45:30

I guess anyone can accidentally brush Enter when typing rm *~ or rm *.o or the like (I normally write echo, and only type the rm when I'm happy with the rest). But perhaps my advice might be useful for others who aren't so disciplined.

@janos 2019-03-09 12:37:20

Double-quote variables used in command parameters

This is a bug:

/bin/rm -i [email protected]

What will happen if you try to delete file a b (with space in the name)? Most likely this:

rm: a: No such file or directory
rm: b: No such file or directory

Always write "[email protected]" instead of unquoted [email protected].

Unfortunately, as you pointed out in a comment, this will cause another problem: arguments containing globs will be taken literally. In short, it's difficult to have the cake and eat it too.

You could mitigate the problem by looping over the arguments, and if you detect a glob, then expand it yourself:

for arg; do
    if [[ $arg == *[*?]* ]]; then
        expanded=($arg)
        echo rm -i "${expanded[@]}"
    else
        echo rm -i "$arg"
    fi
done

This still won't be perfect, because it doesn't handle the case when an argument contains both spaces and globs. A robust solution would take more effort, and not worth doing in Bash. (See this example delegating the hard work to Python.)

Use command to bypass aliases

Don't worry about the absolute path of commands. Use command to bypass aliases:

command rm -i "[email protected]"

Redundant file descriptors

In echo "Abort: ..." 1>&2, the file descriptor 1 is redundant, you can safely omit it.

Preserving the user's environment

This is a minor nitpick. When the alias is executed, it will do set +f, regardless of whatever was the original setting in the shell, which may not be the same. This is really just a minor nitpick, for the record. I wouldn't care about this tiny impractical detail either.

@Weijun Zhou 2019-03-09 15:34:49

I had an issue with patterns containing asterisk (e.g. rm -f .*.swp) and that's why I didn't quote [email protected]. Your other advices are taken.

@janos 2019-03-10 07:21:26

@WeijunZhou That's a good point. See my updated answer.

@Oh My Goodness 2019-03-09 03:18:06

The vulnerability I see is that only the first two arguments are checked. You could check all of them:

rm() { 
    [[ $- == *i* ]] && for arg
    do 
        if [[ $arg = "*" || $arg = "./*" ]]
        then
            # abort
       fi
   done
   # do the rm

This kind of checking will miss other dangerous wildcards like ** or ?*. You can get safer checking by expanding * yourself, then see if the expanded arguments contain that same list of files:

# alias not needed here; we want globs to be expanded
rm() {
    declare -a star=(*)
    declare -a dotslashstar=(./*)
    if [[ "[email protected]" == *"${star[@]}"*  ||  "[email protected]" == *"${dotslashstar[@]}"* ]]
    then
         # abort

... but then you can't (for example) empty a directory full of temp files with rm *.tmp, if *.tmp and * match the same thing.

@Weijun Zhou 2019-03-09 03:20:21

I love your idea of expanding * myself.

Related Questions

Sponsored Content

2 Answered Questions

[SOLVED] Removing commented dead code without removing the legitimate comments

1 Answered Questions

1 Answered Questions

[SOLVED] Bash script for referencing git status output files

  • 2017-08-07 20:28:30
  • C. Sano
  • 726 View
  • 4 Score
  • 1 Answer
  • Tags:   beginner bash

1 Answered Questions

[SOLVED] Bash script to remove unwanted git objects

  • 2015-06-20 15:33:36
  • Elias Van Ootegem
  • 236 View
  • 8 Score
  • 1 Answer
  • Tags:   bash git

Sponsored Content