Delete all files and directories but certain ones using Bash - bash

I'm writing a script that needs to erase everything from a directory except two directories, mysql and temp.
I've tried this:
ls * | grep -v mysql | grep -v temp | xargs rm -rf
but this also keeps all the files that have mysql in their name, that i don't need. it also doesn't delete any other directories.
any ideas?

You may try:
rm -rf !(mysql|init)
Which is POSIX defined:
Glob patterns can also contain pattern lists. A pattern list is a sequence
of one or more patterns separated by either | or &. ... The following list
describes valid sub-patterns.
...
!(pattern-list):
Matches any string that does not match the specified pattern-list.
...
Note: Please, take time to test it first! Either create some test folder, or simply echo the parameter substitution, as duly noted by #mnagel:
echo !(mysql|init)
Adding useful information: if the matching is not active, you may to enable/disable it by using:
shopt extglob # shows extglob status
shopt -s extglob # enables extglob
shopt -u extglob # disables extglob

This is usually a job for find. Try the following command (add -rf if you need a recursive delete):
find . -maxdepth 1 \! \( -name mysql -o -name temp \) -exec rm '{}' \;
(That is, find entries in . but not subdirectories that are not [named mysql or named tmp] and call rm on them.)

You can use find, ignore mysql and temp, and then rm -rf them.
find . ! -iname mysql ! -iname temp -exec rm -rf {} \;

Related

uuidgen and $RANDOM doesn't change in find -exec argument

I want to get all the instances of a file in my macosx file system and copy them in a single folder of an external hard disk.
I wrote a simple line of code in terminal but when I execute it, there is only a file in the target folder that is replaced at every occurrence it finds.
It seems that the $RANDOM or $(uuidgen) used in a single command return only one value used for every occurrence {} of the find command.
Is there a way to get a new value for every result of the find command?
Thank you.
find . -iname test.txt -exec cp {} /Volumes/EXT/$(uuidgen) \;
or
find . -iname test.txt -exec cp {} /Volumes/EXT/$RANDOM \;
This should work:
find ... -exec bash -c 'cp "$1" /Volumes/somewhere/$(uuidgen)' _ {} \;
Thanks to dan and pjh for corrections in comments.
find . -iname test.txt -exec bash -c '
for i do
cp "$i" "/Volumes/EXT/$RANDOM"
done' _ {} +
You can use -exec with +, to pass multiple files to a bash loop. You can't use command subs (or multiple commands at all) in a single -exec.
If you've got Bash 4.0 or later, another option is:
shopt -s dotglob
shopt -s globstar
shopt -s nocaseglob
shopt -s nullglob
for testfile in **/test.txt; do
cp -- "$testfile" "/Volumes/EXT/$(uuidgen)"
done
shopt -s dotglob enables globs to match files and directories that begin with . (e.g. .dir/test.txt)
shopt -s globstar enables the use of ** to match paths recursively through directory trees
shopt -s nocaseglob causes globs to match in a case-insensitive fashion (like find option -iname versus -name)
shopt -s nullglob makes globs expand to nothing when nothing matches (otherwise they expand to the glob pattern itself, which is almost never useful in programs)
The -- in cp -- ... prevents paths that begin with hyphens (e.g. -dir/test.txt) being (mis)treated as options to `cp'
Note that this code might fail on versions of Bash prior to 4.3 because symlinks are (stupidly) followed while expanding ** patterns

Use "rm" command with inverse match

I want to remove all files that do not match R1.fastq.gz in my list of files.
How do I use rm with inverse match?
Use the extended pattern syntax available in bash:
shopt -s extglob
printf '%s\n' !(R1.fastq.gz) # To verify the list of files the pattern matches
rm !(R1.fastq.gz) # To actually remove them.
Or, use find:
find . ! -name R1.fastq.gz -print # Verify
find . ! -name R1.fastq.gz -exec rm {} + # Delete
If your version of find supports it, you can use -delete instead of -exec rm {} +:
find . ! -name R1.fastq.gz -delete
You may want the "invert match" option, grep –v, which means "select lines which do not match the regex." and then remove them.
Something like rm $(ls | grep -v -e "R1.fastq.gz") should do it.
Please, note that this will erase all files in the folder you are on, except R1.fastq.gz

Remove all files except some from a directory

When using sudo rm -r, how can I delete all files, with the exception of the following:
textfile.txt
backup.tar.gz
script.php
database.sql
info.txt
find [path] -type f -not -name 'textfile.txt' -not -name 'backup.tar.gz' -delete
If you don't specify -type f find will also list directories, which you may not want.
Or a more general solution using the very useful combination find | xargs:
find [path] -type f -not -name 'EXPR' -print0 | xargs -0 rm --
for example, delete all non txt-files in the current directory:
find . -type f -not -name '*txt' -print0 | xargs -0 rm --
The print0 and -0 combination is needed if there are spaces in any of the filenames that should be deleted.
rm !(textfile.txt|backup.tar.gz|script.php|database.sql|info.txt)
The extglob (Extended Pattern Matching) needs to be enabled in BASH (if it's not enabled):
shopt -s extglob
find . | grep -v "excluded files criteria" | xargs rm
This will list all files in current directory, then list all those that don't match your criteria (beware of it matching directory names) and then remove them.
Update: based on your edit, if you really want to delete everything from current directory except files you listed, this can be used:
mkdir /tmp_backup && mv textfile.txt backup.tar.gz script.php database.sql info.txt /tmp_backup/ && rm -r && mv /tmp_backup/* . && rmdir /tmp_backup
It will create a backup directory /tmp_backup (you've got root privileges, right?), move files you listed to that directory, delete recursively everything in current directory (you know that you're in the right directory, do you?), move back to current directory everything from /tmp_backup and finally, delete /tmp_backup.
I choose the backup directory to be in root, because if you're trying to delete everything recursively from root, your system will have big problems.
Surely there are more elegant ways to do this, but this one is pretty straightforward.
I prefer to use sub query list:
rm -r `ls | grep -v "textfile.txt\|backup.tar.gz\|script.php\|database.sql\|info.txt"`
-v, --invert-match select non-matching lines
\| Separator
Assuming that files with those names exist in multiple places in the directory tree and you want to preserve all of them:
find . -type f ! -regex ".*/\(textfile.txt\|backup.tar.gz\|script.php\|database.sql\|info.txt\)" -delete
You can use GLOBIGNORE environment variable in Bash.
Suppose you want to delete all files except php and sql, then you can do the following -
export GLOBIGNORE=*.php:*.sql
rm *
export GLOBIGNORE=
Setting GLOBIGNORE like this ignores php and sql from wildcards used like "ls *" or "rm *". So, using "rm *" after setting the variable will delete only txt and tar.gz file.
Since nobody mentioned it:
copy the files you don't want to delete in a safe place
delete all the files
move the copied files back in place
You can write a for loop for this... %)
for x in *
do
if [ "$x" != "exclude_criteria" ]
then
rm -f $x;
fi
done;
A little late for the OP, but hopefully useful for anyone who gets here much later by google...
I found the answer by #awi and comment on -delete by #Jamie Bullock really useful. A simple utility so you can do this in different directories ignoring different file names/types each time with minimal typing:
rm_except (or whatever you want to name it)
#!/bin/bash
ignore=""
for fignore in "$#"; do
ignore=${ignore}"-not -name ${fignore} "
done
find . -type f $ignore -delete
e.g. to delete everything except for text files and foo.bar:
rm_except *.txt foo.bar
Similar to #mishunika, but without the if clause.
If you're using zsh which I highly recommend.
rm -rf ^file/folder pattern to avoid
With extended_glob
setopt extended_glob
rm -- ^*.txt
rm -- ^*.(sql|txt)
Trying it worked with:
rm -r !(Applications|"Virtualbox VMs"|Downloads|Documents|Desktop|Public)
but names with spaces are (as always) tough. Tried also with Virtualbox\ VMs instead the quotes. It deletes always that directory (Virtualbox VMs).
Just:
rm $(ls -I "*.txt" ) #Deletes file type except *.txt
Or:
rm $(ls -I "*.txt" -I "*.pdf" ) #Deletes file types except *.txt & *.pdf
Make the files immutable. Not even root will be allowed to delete them.
chattr +i textfile.txt backup.tar.gz script.php database.sql info.txt
rm *
All other files have been deleted.
Eventually you can reset them mutable.
chattr -i *
I belive you can use
rm -v !(filename)
Except for the filename all the other files will e deleted in the directory and make sure you are using it in
This is similar to the comment from #siwei-shen but you need the -o flag to do multiple patterns. The -o flag stands for 'or'
find . -type f -not -name '*ignore1' -o -not -name '*ignore2' | xargs rm
You can do this with two command sequences.
First define an array with the name of the files you do not want to exclude:
files=( backup.tar.gz script.php database.sql info.txt )
After that, loop through all files in the directory you want to exclude, checking if the filename is in the array you don't want to exclude; if its not then delete the file.
for file in *; do
if [[ ! " ${files[#]} " ~= "$file" ]];then
rm "$file"
fi
done
The answer I was looking for was to run script, but I wanted to avoid deleting the sript itself. So incase someone is looking for a similar answer, do the following.
Create a .sh file and write the following code:
cp my_run_build.sh ../../
rm -rf * cp
../../my_run_build.sh .
/*amend rest of the script*/
Since no one yet mentioned this, in one particular case:
OLD_FILES=`echo *`
... create new files ...
rm -r $OLD_FILES
(or just rm $OLD_FILES)
or
OLD_FILES=`ls *`
... create new files ...
rm -r $OLD_FILES
You may need to use shopt -s nullglob if some files may be either there or not there:
SET_OLD_NULLGLOB=`shopt -p nullglob`
shopt -s nullglob
FILES=`echo *.sh *.bash`
$SET_OLD_NULLGLOB
without nullglob, echo *.sh *.bash may give you "a.sh b.sh *.bash".
(Having said all that, I myself prefer this answer, even though it does not work in OSX)
Rather than going for a direct command, please move required files to temp dir outside current dir. Then delete all files using rm * or rm -r *.
Then move required files to current dir.
Remove everything exclude file.name:
ls -d /path/to/your/files/* |grep -v file.name|xargs rm -rf

SSH command for looping through directories EXCEPT a few specified

Would it be possible to execute a bash command via SSH which loops over multiple directories EXCEPT 3 or 4 that I specify? Something like:
Delete public_html/outdated/ for all directories in /home/ except /home/exception1/ , /home/exception2/ and /home/exception3/
I am on a HostGator Dedicated Linux server.
This is all one line, but I've split it here for readability
find /home
\( -wholename '/home/exception1'
-o -wholename '/home/exception2'
-o -wholename '/home/exception3' \)
-prune -o
-wholename '*/public_html/outdated' -type d
-exec rm -rvf \{} \;
Before running this I strongly suggest replacing the -exec rm -rvf \{} \; bit with -print to verify that it prints only what you want deleted.
Here's how it works: find recursively finds stuff. the stuff in \(...\)' matches directories you want to skip entirely (ie: prune). The standard pattern for using-pruneis to say what you want to prune out, then-prune -o`, and then the stuff you actually want to match.
We want to match public_html/outdated directories, so that's what -wholename '*/public_html/outdated' -type d is for. (-type d means "is a directory").
Finally comes the action we want to perform. Once again, replace this part with -print until you're sure it does what you want.
One caveat: This will spit out a bunch of warnings of the form:
find: `/home/foo/public_html/outdated': No such file or directory
This is because find is trying to walk into those directories it just deleted. You can safely ignore these -- find will continue despite the warnings.
Test first!
shopt -s extglob
rm -rf /home/!(exception1|exception2|exception3)/public_html/outdated/
Here's a simple solution - there's probably better ways to do this.
cd /home
ls */public_html/outdated
will should you all of those; you can use grep -v to remove lines from that
ls */public_html/outdated | grep -Ev ^exception1/ | grep -Ev ^exception2/ | grep -Ev ^exception3/
and then use backticks to feed these into rm -rf
rm -rf `ls */public_html/outdated | grep -Ev ^exception1/ | grep -Ev ^exception2/ | grep -Ev ^exception3/`
Obviously you should run the middle step to verify the list before you actually delete them!
Note that this may not work if any of your directories have spaces in - although usernames in /home shouldn't. The safe way to do that is using find. (There's probably a way to do the exclusion with find but I can never remember.)

shell script to remove selected directories

i have bunch of dirs , say **a, b, c0, d, Z , foo, ** and so on.
I want to remove all the directories except dirs foo, foo2, a and b
can anyone provide me the syntax to do this shell?
Thanks
UPDATE
I just want to say Thank you to all of you for your responses!
echo `ls -1 -d */ | egrep -v '^(foo|foo2|a|b)/$'`
If you are satisfied with the output, replace echo with rmdir (or rm -r, if the directories still contain data).
Probably the easiest way;
mkdir ../tempdir
mv foo foo2 a b ../tempdir
rm *
mv ../tempdir/* .
rmdir ../tempdir
Please note that this deletes also all files, not just directories.
You can use find on a complicated command line, but perhaps the simplest and, more importantly, safest way is to create a file that lists all of the directories you want to remove. Then use the file as input to rm like this:
find . -maxdepth 1 -type d > dirs_to_remove
Now edit the file and take out any directories you want to keep, then use rm:
rm -ir $(<edited_dirs_to_remove)
Note the -i argument. It's optional and forces rm to ask you before deleting each file.
Also note the $(<filename) syntax, which is specific to bash and is equivalent to, but cheaper than $(cat filename).
One of the more powerful ways to do this sort of trick is using find + grep + xargs:
DONT_REMOVE='a|b|c0|d|Z|foo'
find . -type d -print | egrep -v "^\.$DONT_REMOVE\$" | xargs rm -r
The only trick here is making sure the pattern matches only those you don't want to remove.
The above pattern only matches files in the current directory. You can make it more or less
permissive, e.g:
IF_PATH_IS_IMMEDIATE_SUBDIR="^\./($DONT_REMOVE)$"
IF_PATH_ENDS_IN="/($DONT_REMOVE)$"
IF_PATH_CONTAINS="/($DONT_REMOVE)(/.*)?$"
Then pass one of these in your egrep, e.g:
find . -type d -print | egrep -v "$IF_PATH_ENDS_IN" | xargs rm -r
To invert the choice (ie. delete all those items) just remove the -v from the egrep
one way
find . -maxdepth 1 -type d \( ! -name "bar" -a ! -name "foo" -a ! -name "a" -a ! -name "b" \) -delete # to remove files as well, remove -type d
OR try using extglob
shopt -s extglob
rm -rf !(foo|bar|a|b)/ # to delete files as well, remove the last "/"
And yes, this assume you don't want the rest of the directories in your directory except the 4 directories you want.

Resources