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?
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?
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 {} \;
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 {} \;
Other answers concentrate on the behavior of -o and they are right, but this is only half of the story. The other half is the behavior of the implicit -print.
In some circumstances find adds an implicit -print to the expression. This happens when the expression contains neither -exec, -ok, nor (explicit) -print nor -print0. Depending on the implementation of find there may be other primaries that suppress the implicit -print (e.g. -execdir). An expression that triggers this behavior makes find convert a command like:
find paths expression
into:
find paths \( expression \) -print
(where backslashes are to protect ( and ) from being interpreted by your shell). The parentheses are there, this is how the implicit -print works.
-name "*.cpp" -o -name "*.h" you used is such expression, so your:
find . -name "*.cpp" -o -name "*.h"
is equivalent to:
find . \( -name "*.cpp" -o -name "*.h" \) -print
If not the feature of the implicit -print, your find command without -exec would print nothing at all. It's the implicit -print that makes find print what you expect in this case.
Now if you want to use -exec … instead of the implicit -print then you need to keep the parentheses and type them explicitly:
find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;
Your original command with -exec missed these parentheses. This is the moment when the behavior of -o matters. Yes, the parentheses are important when you use -o, this is why you need them in the case with -exec. You don't need to type them in the case without -exec because they appear in the right places automatically due to the implicit -print.
Try to surround your search criteria with brackets as such:
find . \( -name "*.cpp" -o -name "*.h" \) -exec echo {} \;
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 {} \;)