`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