15

If we want to delete all files and directories we use, rm -rf *.

But what if i want all files and directories be deleted at a shot, except one particular file?

Is there any command for that? rm -rf * gives the ease of deletion at one shot, but deletes even my favourite file/directory.

Thanks in advance

RajSanpui
  • 11,556
  • 32
  • 79
  • 146

11 Answers11

31

find can be a very good friend:

$ ls
a/  b/  c/
$ find * -maxdepth 0 -name 'b' -prune -o -exec rm -rf '{}' ';'
$ ls
b/
$ 

Explanation:

  • find * -maxdepth 0: select everything selected by * without descending into any directories

  • -name 'b' -prune: do not bother (-prune) with anything that matches the condition -name 'b'

  • -o -exec rm -rf '{}' ';': call rm -rf for everything else

By the way, another, possibly simpler, way would be to move or rename your favourite directory so that it is not in the way:

$ ls
a/  b/  c/
$ mv b .b
$ ls
a/  c/
$ rm -rf *
$ mv .b b
$ ls
b/
thkala
  • 84,049
  • 23
  • 157
  • 201
  • It can be improved: `find * -maxdepth 0 -name 'b' -prune -o -exec rm -rf {} \;` – maverik Apr 14 '11 at 06:57
  • @maveric: agreed, that's how I'd write it, but I'd rather not get into the various shell intricacies involving quoting and escaping in this answer... – thkala Apr 14 '11 at 07:02
  • Could you not generate the list of files excluding the file to be "protected" and then pipe it to `xargs` rather than spawning `rm` for every match using `-exec`? – Noufal Ibrahim Apr 14 '11 at 07:06
  • @Noufal: not portably - many `xargs` variants out there do not have `-0` which tends to make things easier when dealing with whitespace. – thkala Apr 14 '11 at 07:08
  • @Noufal: see [this](http://stackoverflow.com/q/4262108/507519) there is quite a bit of portability information in the comments. – thkala Apr 14 '11 at 07:18
  • `find *` is terrible. The `*` will be globbed by the shell, and in a crowded directory the number of items could exceed ARGV_MAX. Try to use `find .` whenever possible. – wildplasser Aug 09 '13 at 14:06
  • @wildplasser: that is true, although less likely with [modern Linux kernel versions](http://kernelnewbies.org/Linux_2_6_23#head-ae2a4a278f6bb0de179f59b3dfc3fbdb2b43bf01). That said, when writing this answer I tried to remain as close to the OP's `rm -rf *` as possible, while also avoiding introducing more conditions to protect `.` itself from deletion. – thkala Aug 09 '13 at 22:09
  • Note: there is no possible way to unlink `.`. Note 2: since filesystems can be larger than available memory, ARGV_MAX will always be a problem, waiting in the dark to strike unexpectedly. Avoid it. And finally: you don't **have to** _materialise_ a list if you can serialise/pipe it. (there will be a race condition in either cases, so that cannot be a reason to prefer one over the other) – wildplasser Aug 09 '13 at 22:41
  • The 2nd tip is awesome, I was looking to delete everything except the git files and that worked awesome – pal4life Jul 18 '14 at 19:17
10

Short answer

ls | grep -v "z.txt" | xargs rm

Details:

The thought process for the above command is :

  • List all files (ls)
  • Ignore one file named "z.txt" (grep -v "z.txt")
  • Delete the listed files other than z.txt (xargs rm)

Example

Create 5 files as shown below:

echo "a.txt b.txt c.txt d.txt z.txt" | xargs touch

List all files except z.txt

ls|grep -v "z.txt"

a.txt
b.txt
c.txt
d.txt

We can now delete(rm) the listed files by using the xargs utility :

ls|grep -v "z.txt"|xargs rm
Thyag
  • 1,217
  • 13
  • 14
2

At least in zsh

rm -rf ^filename

could be an option, if you only want to preserve one single file.

Inkane
  • 1,440
  • 1
  • 15
  • 32
2

You can type it right in the command-line or use this keystroke in the script

files=`ls -l | grep -v "my_favorite_dir"`; for file in $files; do rm -rvf $file; done

P.S. I suggest -i switch for rm to prevent delition of important data.

P.P.S You can write the small script based on this solution and place it to the /usr/bin (e.g. /usr/bin/rmf). Now you can use it as and ordinary app:

rmf my_favorite_dir

The script looks like (just a sketch):

#!/bin/sh

if [[ -z $1 ]]; then
    files=`ls -l`
else
    files=`ls -l | grep -v $1`
fi;

for file in $files; do
    rm -rvi $file
done;
maverik
  • 5,508
  • 3
  • 35
  • 55
  • 2
    Hmmm, [parsing the output of ls](http://mywiki.wooledge.org/ParsingLs) is not a good idea - it's a bad habit that should not be propagated to others. And your `for` loops will have issues with filenames that include whitespace... – thkala Apr 14 '11 at 07:06
  • Don't know. Thanks for the hint :) – maverik Apr 14 '11 at 07:12
1

In bash you have the !() glob operator, which inverts the matched pattern. So to delete everything except the file my_file_name.txt, try this:

shopt -s extglob
rm -f !(my_file_name.txt)

See this article for more details: http://karper.wordpress.com/2010/11/17/deleting-all-files-in-a-directory-with-exceptions/

chiborg
  • 26,978
  • 14
  • 97
  • 115
1

If it's just one file, one simple way is to move that file to /tmp or something, rm -Rf the directory and then move it back. You could alias this as a simple command.

The other option is to do a find and then grep out what you don't want (using -v or directly using one of finds predicates) and then rming the remaining files.

For a single file, I'd do the former. For anything more, I'd write something custom similar to what thkala said.

Noufal Ibrahim
  • 71,383
  • 13
  • 135
  • 169
0

I see a lot of longwinded means here, that work, but with a/ b/ c/ d/ e/

 rm -rf *.* !(b*) 

this removes everything except directory b/ and its contents (assuming your file is in b/. Then just cd b/ and

rm -rf *.* !(filename) 

to remove everything else, but the file (named "filename") that you want to keep.

tonybaldwin
  • 171
  • 1
  • 10
0
mv subdir/preciousfile  ./
rm -rf subdir
mkdir subdir
mv preciousfile subdir/

This looks tedious, but it is rather safe

  • avoids complex logic
  • never use rm -rf *, its results depend on your current directory (which could be / ;-)
  • never use a globbing *: its expansion is limited by ARGV_MAX.
  • allows you to check the error after each command, and maybe avoid the disaster caused by the next command.
  • avoids nasty problems caused by space or NL in the filenames.
wildplasser
  • 43,142
  • 8
  • 66
  • 109
0
cd ..
ln trash/useful.file ./
rm -rf trash/*
mv useful.file trash/
Ivan
  • 1,320
  • 16
  • 26
  • NB: This won't work across file system boundaries. e.g. if `trash` is on a different file system to `.` – Dezza Sep 21 '16 at 13:35
  • Dezza, I have never thought about it in this context... Anyway, now I know what 'nota bene' is :) – Ivan Sep 22 '16 at 09:36
0

I don't know of such a program, but I have wanted it in the past for some times. The basic syntax would be:

IFS='
' for f in $(except "*.c" "*.h" -- *); do
  printf '%s\n' "$f"
done

The program I have in mind has three modes:

  • exact matching (with the option -e)
  • glob matching (default, like shown in the above example)
  • regex matching (with the option -r)

It takes the patterns to be excluded from the command line, followed by the separator --, followed by the file names. Alternatively, the file names might be read from stdin (if the option -s is given), each on a line.

Such a program should not be hard to write, in either C or the Shell Command Language. And it makes a good excercise for learning the Unix basics. When you do it as a shell program, you have to watch for filenames containing whitespace and other special characters, of course.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
  • What you are asking for is a simple wrapper for `find` – thkala Apr 14 '11 at 07:11
  • No, I want a general tool. Maybe I want to `cp` some files except for `.svn`, maybe I want to echo their names. The problem with `find` is that it doesn't know the `-maxdepth` option on all systems. See http://pubs.opengroup.org/onlinepubs/009695399/utilities/find.html. – Roland Illig Apr 14 '11 at 07:19
-1

you need to use regular expression for this. Write a regular expression which selects all other files except the one you need.

Anji
  • 725
  • 1
  • 9
  • 27