`find` with multiple `-name` and `-exec` executes only the last matches of `-name`

find . -type f -name "*.htm*" -o -name "*.js*" -o -name "*.txt"

is short for:

find . \( \( -type f -a -name "*.htm*" \) -o \
          \( -name "*.js*" \) -o \
          \( -name "*.txt" \) \
       \) -a -print

That is, because no action predicate is specified (only conditions), a -print action is implicitly added for the files that match the conditions.

(and, by the way, that would print non-regular .js files (the -type f only applies to .htm files)).

While:

find . -type f -name "*.htm*" -o -name "*.js*" -o -name "*.txt" \
  -exec sh -c 'echo "$0"' {} \;

is short for:

find . \( -type f -a -name "*.htm*" \) -o \
       \( -name "*.js*" \) -o \
       \( -name "*.txt" -a -exec sh -c 'echo "$0"' {} \; \)

For find (like in many languages), AND (-a; implicit when omitted) has precedence over OR (-o), and adding an explicit action predicate (here -exec) cancels the -print implicit action seen above. Here, you want:

find . -type f \( -name "*.htm*" -o -name "*.js*" -o -name "*.txt" \) \
  -exec sh -c 'echo "$0"' {} \;

Or:

find . -type f \( -name "*.htm*" -o -name "*.js*" -o -name "*.txt" \) -exec sh -c '
   for i do
     echo "$i"
   done' sh {} +

To avoid running one sh per file.


It is the implied brackets. Add explicit brackets. \( \)

find . -type f \( -name "*.htm*" -o -name "*.js*" -o -name "*.txt" \) -exec sh -c 'echo "$0"' {} \;

or using xargs ( I like xargs I find it easier, but apparently not as portable).

find . -type f \( -name "*.htm*" -o -name "*.js*" -o -name "*.txt" \) -print0 | xargs -0 -n1 echo

Tags:

Find