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 . -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 . '(' -name "*.htm*" -o -name "*.js*" -o -name "*.txt" ')' \
-type f \
-exec sh -c 'echo "$0"' {} \;
Or:
find . '(' -name "*.htm*" -o -name "*.js*" -o -name "*.txt" ')' \
-type f \
-exec sh -c '
for i do
echo "$i"
done' sh {} +
To avoid running one sh
per file.
(-type f
being more expensive than -name
as it potentially implies retrieving information from the inode, is best put after though some find
implementations do reorder the checks internally for optimisation).