32

Is it possible to use 2 commands in the -exec part of find command ?

I've tried something like:

find . -name "*" -exec  chgrp -v new_group {}  ; chmod -v 770 {}  \;

and I get:

find: missing argument to -exec
chmod: cannot access {}: No such file or directory
chmod: cannot access ;: No such file or directory

43

As for the find command, you can also just add more -exec commands in a row:

find . -name "*" -exec chgrp -v new_group '{}' \; -exec chmod -v 770 '{}' \;

Note that this command is, in its result, equivalent of using

chgrp -v new_group file && chmod -v 770 file

on each file.

All the find's parameters such as -name, -exec, -size and so on, are actually tests: find will continue to run them one by one as long as the entire chain so far has evaluated to true. So each consecutive -exec command is executed only if the previous ones returned true (i.e. 0 exit status of the commands). But find also understands logic operators such as or (-o) and not (!). Therefore, to use a chain of -exec tests regardless of the previous results, one would need to use something like this:

find . -name "*" \( -exec chgrp -v new_group {} \; -o -exec chmod -v 770 {} \; \)
  • 4
    +1: Yes, that's the most elegant way to do it. If you can explain, why you use'{}' (apostrophes around the braces), please visit: unix.stackexchange.com/q/8647/4485 – user unknown Aug 4 '11 at 22:37
  • 1
    @user Unfortunately, I don't know if it is still necessary. I did some test just now and haven't come across a situation where it would change anything. I guess it's just "good practice" that will die out. – rozcietrzewiacz Aug 5 '11 at 8:04
  • 2
    The quotes are important for files with spaces in their names. – naught101 Sep 27 '16 at 2:19
17
find . -name "*" -exec sh -c 'chgrp -v new_group "$0" ; chmod -v 770 "$0"' {} \;
  • @Gilles: The wonders of -c's odd handling of $0 make me think this is wrong every time I glance at it, but its definitely correct. – derobert Aug 24 '12 at 16:52
  • I like the explicit shell being defined... – djangofan Aug 24 '12 at 19:05
  • This answer (and Giles's answer) seems like the better answer for the question given the sh -c. – jww Mar 30 at 16:59
14

Your command is first parsed by the shell into two commands separated by a ;, which is equivalent to a newline:

find . -name "*" -exec chgrp -v new_group {}
chmod -v 770 {} \;

If you want to run a shell command, invoke a shell explicitly with bash -c (or sh -c if you don't care that the shell is specifically bash):

find . -name "*" -exec sh -c 'chgrp -v new_group "$0"; chmod -v 770 "$0"' {} \;

Note the use of {} as an argument to the shell; it's the zeroth argument (which is normally the name of the shell or script, but this doesn't matter here), hence referenced as "$0".

You can pass multiple file names to the shell at a time and make the shell iterate through them, it'll be faster. Here I pass _ as the script name and the following arguments are file names, which for x (a shortcut for for x in "$@") iterates over.

find . -name "*" -exec sh -c 'for x; do chgrp -v new_group "$x"; chmod -v 770 "$x"; done' _ {} +

Note that since bash 4, or in zsh, you don't need find at all here. In bash, run shopt -s globstar (put it in your ~/.bashrc) to activate **/ standing for a recursive directory glob. (In zsh, this is active all the time.) Then

chgrp -v new_group -- **/*; chmod -v 770 -- **/*

or if you want the files to be iterated on in order

for x in **/*; do
  chgrp -v new_group -- "$x"
  chmod -v 770 -- "$x"
done

One difference with the find command is that the shell ignores dot files (files whose name begins with a .). To include them, in bash, first set GLOBIGNORE=.:..; in zsh, use **/*(D) as the glob pattern.

  • This answer (and Glenn's answer) seems like the better answer for the question given the sh -c. – jww Mar 30 at 16:58

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.