find command confusion

Earl Olsen

New Member
Joined
Jan 20, 2023
Messages
1
Reaction score
0
Credits
17
Under bash I am running the find command, trying to exclude the
Pictures and Document directories, as well as .hidden directories,
using the '! -path' arguments.

I notice switching the order of the -iname parameters causes
the '! -path" arguments to fail.

Any clues as to what I'm doing wrong?

$ find . ! -path '*/\.*/*' ! -path './Pictures/*' ! -path './Documents/*' -iname '*.bmp' -o -iname '*.ai'
./bin/crud.bmp
./tmp/crud.bmp
./0/python/untitled2.bmp
./0/python/untitled1.bmp
./0/python/test.bmp
$ find . ! -path '*/\.*/*' ! -path './Pictures/*' ! -path './Documents/*' -iname '*.ai' -o -iname '*.bmp'
./.cache/crud.bmp
./bin/crud.bmp
./tmp/crud.bmp
./Pictures/crud.bmp
./Documents/crud.bmp
./0/python/untitled2.bmp
./0/python/untitled1.bmp
./0/python/test.bmp
$
 
Last edited:


You need to group the various conditions together using escaped parentheses \( and \).
E.g.
Bash:
find ./ \( ! -path "*/\.*/*" ! -path "./Pictures/*" ! -path "./Documents/*" \) \( -iname "*.ai" -o -iname "*.bmp \)

In the above, we've grouped the exclusion conditions together AND we've grouped the file-types together too.
That way the find command knows that the exclusions are all related AND that the file-types to look for are also grouped together. That way it will list all .ai and .bmp files that are NOT in hidden directories, or in ./Documents, or ./Pictures. Which is the behaviour that you want.

Without grouping the filters for the exclusions AND the file-types properly, find behaves differently.
If we grouped the exclusions together, but fail to group the files like this:
Bash:
find ./ \( ! -path "*/\.*/*" ! -path "./Documents/*" ! -path "./Pictures/*" \) -iname "*.ai" -o -iname "*.bmp"
Now find will group the exclude directories with the first file filter *.ai and the *.bmp filter will search in all directories. So you'll see a list of .ai files that aren't in any of the excluded directories. But you'll see a list of .bmp files found in ALL directories. Which is NOT what you want. Which is why we need to group things using the parentheses.

Also, it's worth noting that when using the parentheses, remember to put a space between the open parens and the next token in the find command you're building. And a space between the last character in your final condition and the closing parens.
e.g.
Like this:
Bash:
\( -iname "*.ai" -o -iname "*.bmp" \)
NOT like this:
Bash:
\(-iname "*.ai" -o -iname "*.bmp"\)
If you don't have a space separating the escaped parentheses, find will issue an error.

And when using the parens to group your conditions, you can even specify different actions for different file-types too.
For example:
Bash:
find ./ \( ! -path "*/\.*/*" ! -path "./Pictures/*" ! -path "./Documents/*" \) \( \( -iname "*.ai" -print \) -o -iname "*.bmp -exec ls -alh {} + \) \)
In the above, we've told find to exclude the following directories:
Hidden directories, Pictures and Documents.
And we've told it to find all .ai or .bmp files.
For each .ai file found, we just print the file-name using -print. And for each .bmp file found, we use the -exec functionality to list the files using the ls -alh command.
So now we can search for two different types of files AND define different actions for them. And it's because of the way we've grouped the conditions using parentheses, that allow us to do that.

So the parentheses provide you a way of building extremely powerful queries.

As a completely contrived and rather extreme example:
Let's say we expect ALL of our .ai files to be in our Documents directory and ALL of our .bmp files to be in our Pictures directory.

And now we want to use find to try to find any .ai files that are NOT in our Documents directory AND at the same time we want to find any .bmp files that are NOT already in the Pictures directory AND we want to ignore any hidden directories.
Is there a way we can do all of that in one go with a single find command?
The answer is, "Yes, we definitely can!"
Bash:
find ./ ! -path "*/\.*/*" \( \( ! -path "./Documents/*" -iname "*.ai" -print \) -o \( ! -path "./Pictures/*" -iname "*.bmp" -print \) \)
And that should yield a list of ALL .ai files (That are not in hidden directories, or that are not already in the Documents directory) AND ALL .bmp files (that are NOT in hidden directories, or in the Pictures directory).

The initial condition is to exclude hidden directories. Then we have a nested set of parentheses for our file filters.
The first file filter excludes the Documents directory and searches for all .ai files. The second file filter excludes the Pictures directory and searches for all .bmp files.

And the action for each of the filters is the default -print option. I could have just skipped including that because it is the default option. But that's where you could put another action.

So you might decide that you want to delete all of the .ai files that aren't in the Documents directory - so you could use the -delete action. Or you might to decide to use -exec or -execdir functionality in order to perform some other type of processing on the files.
So in place of those -print commands, you could substitute your own actions/commands for find to perform after finding files of that type.

And each of the filters are enclosed in parens and separated by the -o (OR) condition. So we're looking for .ai, or .bmp files. Each of the filters has it's own exclude directory rules alongside the global "no hidden directories".
And after the closing parens for the 2nd filter, we close the parens that surround both file filters.

So find is an insanely powerful, flexible and useful command. It's unlikely that you'll ever need to do anything like the last example. But this just goes to show that you can do some crazy things with find.

Anyway, my first code example is probably the answer to your question - assuming that my fat drummer fingers haven't made any stupid typos. The other examples are just for explanation and demonstration purposes.
 
Last edited:


Follow Linux.org

Members online


Top