I wanted to recursively run chmod go+w in a particular folder including hidden files, and I first tried

find . -name ".*" -o -name "*" -exec chmod go+w {} \;

but I found that it wasn't affecting hidden files. To check myself, I ran just

find . -name ".*" -o -name "*"

and the hidden files were listed. I also noticed that if I excluded the -o -name "*" part it would chmod the hidden files (but exclude non-hidden of course). My last attempt was to use xargs instead

find . -name ".*" -o -name "*" | xargs chmod go+w

which finally worked as expected. What am I doing wrong in the first snippet?

Red Hat Enterprise Linux Server release 6.8 (Santiago)

GNU bash, version 4.3.42(1)-release (x86_64-unknown-linux-gnu)

marked as duplicate by ilkkachu, Sparhawk, Anthony Geoghegan, phk, user34720 May 25 '17 at 10:45

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    Apart from find , chmod with -R, --recursive could also be used right, like chmod -R go+w <dir> ? – sai sasanka May 25 '17 at 6:24
  • 2
    As someone put it, the key to understanding find is to understand that it doesn't find files, it evaluates expressions. It also doesn't really have distinct concepts of "tests" and "actions", they are all operations on the same level. – ilkkachu May 25 '17 at 7:02
  • find locates and reports dot-files by default... – Kevin Jun 22 '17 at 20:49
  • Not in my experience – Brent Jun 22 '17 at 20:55

The solution is to bind the two name tests together with parens.

To illustrate this, let's consider a directory with three regular files:

$ ls -a
.  ..  .hidden1  .hidden2  not_hidden

Now, let's the original command:

$ find . -name ".*" -o -name "*" -exec echo Found {} \;
Found ./not_hidden

Only the non-hidden file is found.

Next, let's add parens to group the two name tests together:

$ find . \( -name ".*" -o -name "*" \) -exec echo Found {} \;
Found .
Found ./not_hidden
Found ./.hidden1
Found ./.hidden2

All files are found.

The solution is to use parens.

More details

In the original command, there is not operator between -name "*" and -exec ... \;. Consequently, find assumes the default operator which is logical-and. Because logical-and binds tighter than logical-or (-o), this means that the command is interpreted as:

find . \( -name ".*" \) -o \( -name "*" -exec echo Found {} \; \)

This means that the exec is run only if the first name condition failed to match.

For more information, see the OPERATORS section in man find.

What happens without -exec

Let's try using a simple -print:

$ find . -name ".*" -o -name "*" -print

As you can see, -print bound to the -name "*" with the implicit logical-and as above.

But, consider what happens without any action specified:

$ find . -name ".*" -o -name "*"

Here, all files were found. The reason is that, in this version, -o is the only operator.

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