I'm trying to build a find command with multiple conditions that lead to multiple actions. It was suggested to me that I should use only one find command for efficiency. Here is what I have:

    find $target \
            \( ! -group $project -exec chgrp $project "{}" \;       \) , \
            \( ! -user $owner -exec chown $owner "{}" \;            \) , \
            \( ! -perm "$perms" -exec chmod "$perms" "{}" \;        \) , \
            \( -type d -exec chmod g+s "{}" \; \)

It seems to actually do something, however I get:

find: invalid expression


(: command not found

in the script I am trying to execute

  • 3
    Wow... Is this really worth it? Why not a simple chmod -R, chown -R etc? – terdon Sep 13 '13 at 21:48
  • Yes, it's worth it. Dealing with a lot of files here, and a flat chmod/chown -R takes significantly longer--like the better part of a day. I don't know why. Maybe find only has to stat each file once for all 4 conditions when combining them this way. But it's certainly a lot faster on the filesystem we're using. – Angelo Sep 13 '13 at 22:35
  • I get a different error when copy pasting, could you give us an minimal non-working example using actual values instead of variables? It might be the variables that are complicating things. – terdon Sep 13 '13 at 22:39
  • You're probably right, I'm unable to reproduce the error when I remove the line with the $perms variable. But even if I comment that line out, I still have an error, so I'm not sure what it is. perms is ug+rX,go-w,o-rx – Angelo Sep 13 '13 at 22:52
  • Hang on, sorry I had not read your comment carefully enough. If you still have an error, please edit your question using the actual strings involved and we'll see if we can help. – terdon Sep 13 '13 at 23:47

The error message indicates that ( was executed as a command, which means that one of the backslashes used for line continuation are in fact not the last character of the line. Make sure that there is no whitespace. Make sure that you're using Unix line endings (LF only, no CR).

The complaint from find about an invalid expression is due to those commas. Just remove them.

find "$target" \
        \( ! -group "$project" -exec chgrp "$project" {} \;   \) \
        \( ! -user "$owner" -exec chown "$owner" {} \;        \) \
        \( ! -perm "$perms" -exec chmod "$perms" {} \;        \) \
        \( -type d -exec chmod g+s {} \; \)

You will probably save time running the commands once per batch of files instead of once per file. This is not guaranteed here because the chown, chgrp and chmod calls may proceed at different rates so directory entries may be evicted from the cache, but I'd give it a go.

find "$target" \
        \( ! -group "$project" -exec chgrp "$project" {} +   \) \
        \( ! -user "$owner" -exec chown "$owner" {} +        \) \
        \( ! -perm "$perms" -exec chmod "$perms" {} +        \) \
        \( -type d -exec chmod g+s {} + \)

chgrp, chmod and chown do nothing if the file already has the right metadata, so you could call them unconditionally. Running them needlessly does cause more calls, however. The utilities will call stat again after find has done so, but the inodes are highly likely to still be in the cache, so this may be worthwhile. You can save a call by combining chgrp into chown.

find "$target" -exec chown "$owner:$project" {} + \
        -exec chmod "$perms" {} + \
        -type d -exec chmod g+s {} +
  • Will this still be faster if you remove the not conditions from find? I had assumed the speed-up was because the chmod etc commands were only run if necessary. In your last example, find does not check for that so won't it be equivalent to chown -R foo:bar;chmod -R $perms; chmod g+s? Apart from the last of course that does limit it to directories only. – terdon Sep 14 '13 at 0:25
  • @terdon The relative performance of the last command depends a lot on how many files need to be modified, so I recommend that Angelo tries both with his data. Using find instead of chown -R …; chmod -R … makes a difference because the data is read from the disk only once: after find has called stat, it's still in the cache for chown` and chmod. – Gilles Sep 14 '13 at 0:33
  • Of course, should have realized that if find is checking the files it will need to read the data so having the conditionals won't make much difference. I see, thanks. – terdon Sep 14 '13 at 0:48
  • Thanks, having extra whitespace at the end of a line seems to explain what I was experiencing. – Angelo Sep 14 '13 at 2:01
  • 1
    @Angelo find … -exec somecommand {} + executes somecommand with multiple matching files passed as separate arguments (like xargs does). find … -exec somecommand {} \; executes somecommand once per matching file. See the find documentation for more information. – Gilles Sep 18 '13 at 17:20

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.