Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

There are plenty of situations where the use of a * is virtually inevitable - e.g. rm -rf * in a folder that holds thousands of subfolders and files.

But what if you want to exclude just one or two files or folders from the rm command? I've googled my way around and only found quite complicated solutions like find . -depth -not \( -name 'one' -o -name 'two' \ -o -name 'three' \) -exec rm {} \; as stated here.

Is there a possibility to do this in an easier way - without that detour to find? E.g. rm -rf --exclude='one' --exclude='two' --exclude='three' * like in rsync or just rm -rf -e 'one','two','three' *?

Maybe even a general possibility to exclude things from * (so other commands like cp, mv, ... don't have to implement their own)? Something like *{'one','two','three'} or so?

share|improve this question
3  
If you want to keep only one or two files then the simplest and easiest way would be to move those files to another location, wipe all remaining files and move the ones you kept back. If you want to keep a lot more files I would use find with the --delete option (no need to execute rm for each file. That is needless overhead). –  Hennes Aug 6 '13 at 8:42
    
That would be a possibility, but it is nearly as elaborate as the one I mentioned. I know I could combine the commands to something like mv -t /tmp one two three && rm -rf * && mv -t . /tmp/one /tmp/two /tmp/three, but I would prefer a solution giving the possibility to explicitely exclude something from *. There will surely be situations where moving or copying the files to another destination won't be an option. –  David Aug 6 '13 at 8:57
2  
It is, which is why I did not post it as an answer. Merely as a less complex way of doing things (three very simple commands vs one somewhat more complex ones). I hate complexity in combination with rm. –  Hennes Aug 6 '13 at 9:30
    
Thanks, though. :) –  David Aug 6 '13 at 9:38

4 Answers 4

up vote 29 down vote accepted

For Bash there is a shell option called extglob that is deactivated by default to keep compatibility with standard shell syntax. Extglob complements the syntax by additional operators like !(), ?(), @() and some more.

To switch extglob on, type shopt -s extglob. To keep it switched on for the current user type echo 'shopt -s extglob' >> ~/.bashrc.

For the rm example: With extglob you can use

rm -rf !(one|two|three)
share|improve this answer
    
Seems like a good solution, but I also don't want to switch that on and off everytime I get into a situation like the described one. –  David Aug 6 '13 at 8:51
3  
@David: you could put the shopt thing into your ~/.bashrc, it cannot hurt. –  enzotib Aug 6 '13 at 8:53
    
If I get it right, the extglob option does not change the effect of * at all, but instead adds similar indicators (like the !) that have additional functions? In that case it would be a lovely solution. –  David Aug 6 '13 at 9:09
1  
@David: Exactly. –  choroba Aug 6 '13 at 9:19
1  
@David To keep compatibility with standard shell syntax. There are likely other situations where behaviour may change. –  gerrit Aug 6 '13 at 10:00

I've built except exactly for that (sry that I wasn't able to add documentation yet).

I you have a folder like the following:

$ ls
a b c d

Typing except b d will give you a and c

$ except b d
ac

Now you can pipe that to rm.

except b d | xargs -0 rm

This may be hard to remember, so why not just build a script on top of except, called rm-except:

#!/usr/bin/env bash

except "$@" | xargs -0 rm

Similarly easy is ls-except:

#!/usr/bin/env bash

except "$@" | xargs -0 ls
share|improve this answer
3  
This is nice, but not really needed when you know about extglob - askubuntu.com/a/329233/138369 –  David Aug 6 '13 at 9:46

As an alternative to extglob (though that is a very good answer and everyone should have shopt -s extglob globstar in their .bashrc), you can use the $GLOBIGNORE global variable. Let's say you want to get every file except 'foo.txt' and 'bar baz.txt':

GLOBIGNORE=foo.txt:'bar baz.txt'

...however, this will turn the shell option dotglob on, which means that * will match files beginning with a dot (which are normally hidden). So you actually need two commands:

GLOBIGNORE=foo.txt:'bar baz.txt'
shopt -u dotglob

Since this is a global variable, it will affect every glob you use until $GLOBSTAR is unset either by logging out or with

GLOBIGNORE=

It will also only work on the literal strings passed to the variable. You can see what I mean by setting $GLOBIGNORE and looking at the difference between these commands:

printf '%s\n' *
printf '%s\n' ./*
share|improve this answer

You can filter the output using grep -vE:

{giacomo@state-monad ~}$mkdir test_globs
{giacomo@state-monad ~}$cd test_globs
{giacomo@state-monad ~/test_globs}$for i in {1..100}
> do
>     touch file$i
> done
{giacomo@state-monad ~/test_globs}$touch one two three
{giacomo@state-monad ~/test_globs}$rm $(ls | grep -vE 'one|two|three')
{giacomo@state-monad ~/test_globs}$ls
one  three  two

The -E is for using extended regexes, while -v inverts the matches.


I assume you aren't dumb, and all the filenames/directory names match [a-zA-Z0-9_.-].

share|improve this answer
4  
-1 for spreading dangerous practices. Do not parse ls. Due to word splitting, you'll also get unwanted behavior for files that contain spaces. –  ignis Aug 6 '13 at 11:54
    
@ignis I don't see why this is dangerous. The only way to make thuis dangerous is given dumb names to files. I follow a strict naming convention with my files and directories hence this is not dangerous in my environment. Anybody who uses characters outside [a-zA-Z0-9_-.] for its filenames ought to be punished. –  Bakuriu Aug 6 '13 at 11:57
    
@ignis Give me an example of filename that is dangerous to use and that matches the convention I assumed in the answer. –  Bakuriu Aug 6 '13 at 12:05
4  
When I wrote the comment, you had not yet stated the policy that you follow on your own machine. touch * would delete all files, but * is not in the convention. There are still the points about parsing ls (which is a bad practice) and the problem of spaces in filenames, which I'd hardly consider "dumb" at all, so I'll leave the -1. –  ignis Aug 6 '13 at 12:14
2  
The motto of Ubuntu is 'Linux for Human Beings'. Acting sanctimonious towards people for using human-readable file names is ridiculous, especially when you can just use printf '%s\n' * instead of ls -- or even just use ls -1 (that's the number one) -- to output one file per line and avoid the word-splitting problem (this would still break on files containing newlines, but I've only ever seen those created specifically to break commands like this). –  evilsoup Aug 6 '13 at 16:26

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.