delete all directories except one
With bash you can do this with the extglob option of shopt .
shopt -s extglob cd parent rm -rf !(four)
With a posix shell I think you get to use a loop to do this
for dir in ./parent/*; do [ "$dir" = "four" ] && continue rm -rf "$dir" done
or use an array to run rm only once (but it requires arrays or using «$@» )
arr=() for dir in ./parent/*; do [ "$dir" = "four" ] && continue arr+=("$dir") done rm -rf "$"
for dir in ./parent/*; do [ "$dir" = "four" ] && continue set -- "$@" "$dir" done rm -rf "$@"
find ./parent -mindepth 1 -name four -prune -o -exec rm -rf <> \;
or (with find that has -exec + to save on some rm executions)
find ./parent -mindepth 1 -name four -prune -o -exec rm -rf <> +
Oh, or assuming the list of directories isn’t too large and unwieldy I suppose you could always use
then delete the parent/four entry from the command line and hit enter where * is readline’s default binding for glob-expand-word .
@erip Heh, yeah that’s certainly a much easier route in many cases. Though extglob is almost as simple.
This is just the correct form of that find example from OP find ./ -mindepth 1 -name four -prune -o -exec rm -rf <> \;
Easiest and practical way in my opinion is to move the exception to parent folder, delete everthing move it back
mv exceptionFolder .. rm -rf * mv ../exceptionFolder .
Edit: If there is no parent directory
mkdir todelete mv * todelete/ mv todelete/exceptionFolder . rm -rf todelete
rm -r $(ls -A | grep -v important_folder)
ls -A lists all files and folders (other than . and ..).
grep -v expression removes anything matching the grep expression from the list.
You can also add the -E flag to grep to use capture groups and | and then you can easily add more folders: grep -vE «(file1)|(file2)» finds everything except those two files.
You can also add the -x flag to grep to match full lines (in case any other folders include the name of the folder to save)
find . -maxdepth 1 ! -name foldder_name -type d -not -path '.' -exec rm -rf <> +
find — Its command search files/directories
-maxdepth 1 — Do not find for foldder_name subcategories
. ! -name foldder_name — It means search in current directory . and do not fide this file with his actual name: ! -name foldder_name
-type d — find only directories
-not -path ‘.’ — do not found hidden folders
-exec rm -rf <> + — remove all found data
Delete all folders inside a folder except one with specific name
I need to delete all folders inside a folder using a daily script. The folder for that day needs to be left. Folder ‘myfolder’ has 3 sub folder: ‘test1’, ‘test2’ and ‘test3’ I need to delete all except ‘test2’. I am trying to match exact name here:
find /home/myfolder -type d ! -name 'test2' | xargs rm -rf
find /home/myfolder -type d ! -name 'test2' -delete
6 Answers 6
This will delete all folders inside ./myfolder except that ./myfolder/test2 and all its contents will be preserved:
find ./myfolder -mindepth 1 ! -regex '^./myfolder/test2\(/.*\)?' -delete
How it works
- find starts a find command.
- ./myfolder tells find to start with the directory ./myfolder and its contents.
- -mindepth 1 not to match ./myfolder itself, just the files and directories under it.
- ! -regex ‘^./myfolder/test2\(/.*\)?’ tells find to exclude ( ! ) any file or directory matching the regular expression ^./myfolder/test2\(/.*\)? . ^ matches the start of the path name. The expression (/.*\)? matches either (a) a slash followed by anything or (b) nothing at all.
- -delete tells find to delete the matching (that is, non-excluded) files.
Example
Consider a directory structure that looks like;
$ find ./myfolder ./myfolder ./myfolder/test1 ./myfolder/test1/dir1 ./myfolder/test1/dir1/test2 ./myfolder/test1/dir1/test2/file4 ./myfolder/test1/file1 ./myfolder/test3 ./myfolder/test3/file3 ./myfolder/test2 ./myfolder/test2/file2 ./myfolder/test2/dir2
We can run the find command (without -delete ) to see what it matches:
$ find ./myfolder -mindepth 1 ! -regex '^./myfolder/test2\(/.*\)?' ./myfolder/test1 ./myfolder/test1/dir1 ./myfolder/test1/dir1/test2 ./myfolder/test1/dir1/test2/file4 ./myfolder/test1/file1 ./myfolder/test3 ./myfolder/test3/file3
We can verify that this worked by looking at the files which remain:
$ find ./myfolder ./myfolder ./myfolder/test2 ./myfolder/test2/file2 ./myfolder/test2/dir2
Excellent work!, but sorry: that will remove all files inside ./myfolder . You need a missing (IMvhO) -type d for only directories.
Ok, this should work as you want: find ./myfolder -depth -mindepth 1 -maxdepth 1 -type d ! -regex ‘^./myfolder/test2\(/.*\)?’
shopt -s extglob rm -r myfolder/!(test2)/
$ tree myfolder/ myfolder/ ├── test1 │ └── file1 ├── test2 │ └── file2 └── test3 └── file3 $ echo rm -r myfolder/!(test2) rm -r myfolder/test1 myfolder/test3 $ rm -r myfolder/!(test2) $ tree myfolder/ myfolder/ └── test2 └── file2 1 directory, 1 file
tl;dr
find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 \ -exec echo rm -rf '<>' \;
Remove echo if satisfied with the list of files.
Using -mindepth 1 will ensure that the top directory is not selected.
$ find ./myfolder -mindepth 1 -type d ./myfolder/test2 ./myfolder/test2/one ./myfolder/test2/two ./myfolder/test ./myfolder/test/a1 ./myfolder/test/a1/a2 ./myfolder/test/a1/a2/a3
But a -not -name test2 will not avoid subdirs inside test2 :
$ find ./myfolder -mindepth 1 -type d -not -name 'test2' ./myfolder/test2/one ./myfolder/test2/two ./myfolder/test ./myfolder/test/a1 ./myfolder/test/a1/a2 ./myfolder/test/a1/a2/a3
To do that, you need something like prune:
$ find ./myfolder -mindepth 1 -name test2 -prune -o -type d -print ./myfolder/test ./myfolder/test/a1 ./myfolder/test/a1/a2 ./myfolder/test/a1/a2/a3
But do not use delete , as it implies depth and that will start erasing from the longest path:
$ find ./myfolder -depth -mindepth 1 -name test2 -prune -o -type d -print ./myfolder/test/a1/a2/a3 ./myfolder/test/a1/a2 ./myfolder/test/a1 ./myfolder/test
Either use rm -rf (remove the echo if you want to actually erase):
$ find ./myfolder -mindepth 1 -name test2 -prune -o -type d -exec echo rm -rf '<>' \; rm -rf ./myfolder/test rm -rf ./myfolder/test/a1 rm -rf ./myfolder/test/a1/a2 rm -rf ./myfolder/test/a1/a2/a3
Or, also use maxdepth if all you need is to delete directories (and everything inside) (remove the echo to actually erase):
$ find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 -exec echo rm -rf '<>' \; rm -rf ./myfolder/test
A -delete will still fail if the directory is not empty:
$ find ./myfolder -mindepth 1 -maxdepth 1 -type d -not -name test2 -delete find: cannot delete ‘./myfolder/test’: Directory not empty
Using find — Deleting all files/directories (in Linux ) except any one
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
11 Answers 11
find can be a very good friend:
$ ls a/ b/ c/ $ find * -maxdepth 0 -name 'b' -prune -o -exec rm -rf '<>' ';' $ ls b/ $
- 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/
@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.
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: not portably — many xargs variants out there do not have -0 which tends to make things easier when dealing with whitespace.
Short answer
ls | grep -v "z.txt" | xargs rm
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)
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 :
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:
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;
Hmmm, parsing the output of ls 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.
could be an option, if you only want to preserve one single file.
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 find s predicates) and then rm ing 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.
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)
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.