8

Why in Linux (Debian 8)

touch 1.cpp 1.h
find . -name "*.cpp" -o -name "*.h" -exec echo {} \;

outputs only 1.h while

find . -name "*.cpp" -o -name "*.h"

outputs both? Is it a bug or feature?

4 Answers 4

3

I think once you used -or operator, then you've to keep it consistent in order to avoid ambiguous order of logical operations when you have multiple conditions connected using logical OR.

It seems the -exec part is grouped together with the second -name "*.h".

So in order to make it work correctly, you've to add the brackets as below:

find . '(' -name '*.cpp' -o -name '*.h' ')' -exec echo {} ';'

Remember: The parentheses must be quoted or escaped with a backslash to prevent them from being interpreted as special shell characters.

Alternatively combine few extensions into one by using -regex:

find . ! -regex ".*\.\(cpp\|h\)" -exec echo {} \;
2

Neither. It's the syntax of the options that is "wrong". find evalutates sequencially. Hence, it evaluates the first expression (-name "*.cpp") then encounters a -o flag. If the first expression is true, find will not continue evaluating the second one (-name "*.h" -exec echo {} \;), instead does nothing. You see, the whole part after -o is one expression. Therefore, this is only executed for files that match the second expression. That's why you see only the 1.h file, which passes only the second expression. See the find manpage:

   expr1 -o expr2
          Or; expr2 is not evaluated if expr1 is true.

Why is this useful? Consider the following:

find /path -exec test_file_for_something {} -print \; -o -name "xyz" -exec ls -l {} \;

In this find statement the file is given to test_file_for_something as a parameter. Now, depending on that commands return code the first expression is true (then -print is executed and it ends there) or false (then the second expression after the -o flag is evaluated). And if that one is true (the name is xyz), then -exec is executed.

For your problem you can instead use this to group the elements together as one expression:

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;
1

Try to surround your search criteria with brackets as such:

find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;
0

That is the way find works with it's operators.

See http://linux.die.net/man/1/find section OPERATORS

OPERATORS

Listed in order of decreasing precedence: ( expr ) Force precedence. Since parentheses are special to the shell, you will normally need to quote them. Many of the examples in this manual page use backslashes for this purpose: '(...)' instead of '(...)'. ! expr True if expr is false. This character will also usually need protection from interpretation by the shell.

-not expr Same as ! expr, but not POSIX compliant. expr1 expr2 Two expressions in a row are taken to be joined with an implied "and"; expr2 is not evaluated if expr1 is false. expr1 -a expr2 Same as expr1 expr2. expr1 -and expr2 Same as expr1 expr2, but not POSIX compliant. expr1 -o expr2 Or; expr2 is not evaluated if expr1 is true. expr1 -or expr2 Same as expr1 -o expr2, but not POSIX compliant. 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. The comma operator can be useful for searching for several different types of thing, but traversing the filesystem hierarchy only once. The -fprintf action can be used to list the various matched items into several different output files.

This should give you the result you want:

find . \( -name '*.cpp' -o -name '*.h' \) -exec echo {} \;

Your command is doing this (command won't work, just to show you the logic):

find . -name '*.cpp' (-o -name '*.h' -exec echo {} \;)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .