134

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)

0

2 Answers 2

176
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).

1
  • In some of those sh -c uses, you need to add the zeroth argument for sh (though in others you included it already). Commented Dec 9, 2013 at 23:14
43

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
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .