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

  • 1
    Note that -name "*" matches every name, so it's a no-op test that can be removed. Also, both chmod and chgrp has a -R option for recursive operation.
    – Kusalananda
    Dec 18, 2020 at 20:20

3 Answers 3


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 -true \) -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 Aug 4, 2011 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. Aug 5, 2011 at 8:04
  • 2
    The quotes are important for files with spaces in their names.
    – naught101
    Sep 27, 2016 at 2:19
  • @naught101 No, the quotes may be needed in some non-standard shells, such as fish and csh, but are definitely not needed in POSIX-like shells (sh, dash, bash, zsh, ksh, yash).
    – Kusalananda
    Dec 18, 2020 at 20:22
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, 2012 at 16:52
  • I like the explicit shell being defined...
    – djangofan
    Aug 24, 2012 at 19:05
  • This answer (and Giles's answer) seems like the better answer for the question given the sh -c.
    – user56041
    Mar 30, 2019 at 16:59
  • 1
    Instead of passing the argument in as $0, pass sh as the first argument and then reference $1, i.e. find -exec sh -c 'chgrp...' sh {} \; The value of $0 is used e.g. as the command name when printing error messages from the shell. Jul 15, 2021 at 1:39
  • @Angel, what does this answer do that's different from the advice in the linked answer? Dec 19, 2021 at 1:51

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"

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.
    – user56041
    Mar 30, 2019 at 16:58
  • Both chgrp and chmod has a -R option for recursive operation.
    – Kusalananda
    Dec 18, 2020 at 20:35

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.