Bash and Zsh – Make `rm` Interactive with Globbing

bashrmwildcardszsh

Whenever I rm multiple items at once with shell globbing and there's even the slightest possibility that the pattern expands to more than I expect it to, I always try to remember to add -i, but (of course) I could forget and accidentally blow away something useful. Some people try to solve this problem with an alias, such as alias rm='rm -i' but I hate the idea of making rm always interactive, because then I'll get into the habit of using -f all the time, which will obviously defeat the whole purpose.

What I'd like to do is make rm only use -i when I use globbing, and for -f of course to override -i, so I can still blow away whole directories with the usual rm -rf junk.

Therefore rm *blah* would really be rm -i *blah* but rm blah would work exactly as typed.

In case there's a difference in how this might be accomplished in different shells, I will chose an answer that supplies methods for both bash and zsh over answers that only cover one or the other.


Note: I'm not actually convinced this is necessarily a good idea; I'm just asking if it is possible. If you think this is a stupid idea, I would be very happy to hear the reasoning behind your opinion, but please don't post such comments in the answer field. Real answers either tell me how to do this or explain that/why it is not possible (or possibly offer an alternative method to getting a similar result). But if you want to tell me why this is dumb, please post as a comment. I'll upvote well-reasoned comments even if I disagree with the conclusion.

Best Answer

It's not easy. The problem is that when you do:

rm -- *.txt

rm just sees the list of files, not the globbing pattern which has already been expanded by the shell.

What you could do is (zsh):

alias rm='noglob rm'

This tells zsh that patterns should not be expanded when calling rm. Then you can redefine rm as a function that does the expansion itself and adds the "-i" if need be something like:

'rm'() {
  [[ "$*" = *[*[?]* ]] && set -- -i "$@"
  command rm $~@
}

The problem with that approach though is that this new "rm" doesn't see the difference between

rm *

and

rm '*'

To remove the file called *, you'd have to write it:

rm [*]
Related Question