65

When I'm using

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

it finds all the types of file. But when I add -exec at the end:

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

it seems it only prints .txt files. What am I doing wrong?

Note: using MINGW (Git Bash)

  • Hint: the first command will also print directories whose names match *.js* or *.txt. – Wildcard Sep 8 '17 at 0:00
87
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.

  • In some of those sh -c uses, you need to add the zeroth argument for sh (though in others you included it already). – James Youngman Dec 9 '13 at 23:14
  • That's a great answer! – Marinos An Feb 28 '18 at 21:05
26

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.