bash find and boolean operators

Just because it took me way too long this morning to figure out how Boolean operators work with find.

Suppose I want to find the files with .png and .jpg extensions.

Its not

$ find /path/ -name '*.png' -and -name '*.jpg'

but

$ find /path/ -name '*.png' -or -name '*.jpg'

The “-and” refers to one set of file names where both conditions are met (as if we didn’t use the boolean at all). The “-or” says I’m looking for either/both of two sets.

So to explain further:

$ find . -iname david\ bowie\*ogg -type f > david_bowie.m3u

(This will find any files called, in one way or the other, “david bowie….ogg” and write that list to a .m3u file.)

Per above this is semantically equivalent to:

$ find . -iname david\ bowie\*ogg -and -type f > david_bowie.m3u

And equivalent to:

$ find . -iname "david bowie*ogg" -a -type f > david_bowie.m3u

So the and isn’t usually so helpful – its implied by the succession or arguments.

If you wanted to find all files that aren’t owned by a user, you could use the -not operator:

$ find -not -user jfishwick

It’s also possible to have one invocation of find perform more than one task with using “and,” “or,” or “not.” Use the “,” operator. The gotcha here is that the value of the list returned is the value of the last expression. The other values are thrown away unless we do something with them immediately.

So, to compile two lists, one containing the names of all .php files and the other the names of all .js files use:

$ find ~ -type f \( -name \*.php -fprint php_files , 
                    -name \*.js -fprint javascript_files \)

A summary of the operators, Listed in order of decreasing precedence:

( expr )

Force precedence.

-not expr

True if expr is false. With find, matches everything but this expressed value.

! expr

Same as -not expr.

expr1 -and expr2

expr2 is not evaluated if expr1 is false. So with find, only returns matches that meet both criteria.

expr1 expr2

And (implied); same as above

expr1 -a expr2

Same as expr1 expr2.

expr1 -or expr2

Or; expr2 is not evaluated if expr1 is true. With find return if either expression matches the list.

expr1 -o expr2

Same as expr1 -or expr2.

expr1 , expr2

List; both expr1 and expr2 are always evaluated. The value of expr1 is discarded; the value of the list is the value of expr2.

One last note, using -exec at the end of a long boolean chain will only effect the last option and expression.

If I try

$ find -iname \*asx -or -iname \*clean.txt -or -iname \*dvdIt.txt -or -iname \*.qt.\* -or -iname \*.rt\* -or -iname \*scc -or -iname \*sdf -or -iname \*smi -or -iname \*srt -or -iname \*stl -exec rm -rf {} \;

only the *.stl files would get deleted. To pass everything on, use xargs, natch.

$find -iname \*asx -or -iname \*clean.txt -or -iname \*dvdIt.txt -or -iname \*.qt.\* -or -iname \*.rt\* -or -iname \*scc -or -iname \*sdf -or -iname \*smi -or -iname \*srt -or -iname \*stl | xargs rm -rf

What say you?