Why does `find` in Linux skip expected results when `-o` is used?

7

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?

mastan

Posted 2015-09-02T10:21:52.470

Reputation: 73

Answers

2

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 {} \;

kenorb

Posted 2015-09-02T10:21:52.470

Reputation: 16 795

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 {} \;

chaos

Posted 2015-09-02T10:21:52.470

Reputation: 3 704

1

Try to surround your search criteria with brackets as such:

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

Art Gertner

Posted 2015-09-02T10:21:52.470

Reputation: 6 417

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 {} \;)

HoD

Posted 2015-09-02T10:21:52.470

Reputation: 2 282