With GNU find
:
First some setup, make a directory, create 100 empty files in it, and append "DELETEME" to some of them:
mkdir files
touch files/{001..100}
for i in 001 010 020 030 040 050 065 077 088 099 ; do echo "DELETEME" >> files/$i ; done
Next, list the files containing DELETEME:
find ./files/ -type f -exec grep -q DELETEME {} \; -print
./files/050
./files/001
./files/065
./files/020
./files/040
./files/030
./files/088
./files/077
./files/099
./files/010
This prints all the files where grep -q DELETEME
returned true. Note that it is important to use \;
to end the -exec
here (and not +
), as each file needs to be tested individually (otherwise the exit code from grep -q
will be for the entire batch of files it is currently being run for).
find
's predicates are, by default, ANDed together, so a rough English translation of what this does is "grep AND delete any files where the previous -exec
returned true".
This will work with filenames containing any valid characters, including shell metacharacters, newlines and other whitespace.
And, finally, to delete the matches, use -delete
instead of -print
:
find ./files/ -type f -exec grep -q DELETEME {} \; -delete
If you're using a version of find
that doesn't have -delete
, you can use -exec
again instead:
find ./files/ -type f -exec grep -q DELETEME {} \; -exec rm {} +
We can use +
with the second -exec
here because the filenames don't need to be deleted individually, it's better & faster to delete them in a batch or batches.
You can, of course, use any of find's other predicates - e.g. -maxdepth 1
to prevent recursion.
BTW, if you wanted to delete files that contained only "DELETEME" and nothing else, you'd need to do it more like this:
First make sure we have at least one such file.
$ echo "DON'T DELETEME" > files/001
Print a list of files containing DELETEME plus anything else:
$ find ./files/ -type f -exec grep -q '^DELETEME$' {} \; -exec grep -vq '^DELETEME$' {} \; -print
./files/001
In this case, we're using the regexp ^DELETEME$
instead of just DELETEME
- that's because we want to match only lines containing DELETEME without anything else on the line. If you want to allow optional leading and/or trailing whitespace on a line, use ^[[:space:]]*DELETEME[[:space:]]*$
Then we AND that with another -exec grep
that matches files containing anything other than ^DELETEME$
.
We can now move on to listing the files containing DELETEME except for those containing anything else, using !
(boolean NOT) to negate the second -exec
:
$ find ./files/ -type f -exec grep -q '^DELETEME$' {} \; ! -exec grep -vq '^DELETEME$' {} \; -print
./files/050
./files/065
./files/020
./files/040
./files/030
./files/088
./files/077
./files/099
./files/010
Note that file ./files/001
is missing from this list, which is exactly what we want.
Also note the !
immediately before the second -exec
predicate. It's very important and also very easy to miss, so I'm explicitly pointing it out.
In this example, the first -exec grep
gets the list of files containing ^DELETEME$
, then it NOT ANDs that with files containing anything other than ^DELETEME$.
The -print
can be replaced with -delete
or -exec rm {} +
to actually delete the files.
PS: For anything more complicated than this, I'd be inclined to write a perl script, probably using the File::Find module.
Some things are just much easier to write in a procedural language style than with an extremely long find
command line with chains of predicates tied together with boolean operators (it becomes very easy to lose track of the chain of logic).