13

Using the following command, could someone please explain what exactly is the purpose for the ending curly braces ({}) and plus sign (+)?

And how would the command operate differently if they were excluded from the command?

find . -type d -exec chmod 775 {} +
17

The curly braces will be replaced by the results of the find command, and the chmod will be run on each of them. The + makes find attempt to run as few commands as possible (so, chmod 775 file1 file2 file3 as opposed to chmod 755 file1,chmod 755 file2,chmod 755 file3). Without them the command just gives an error. This is all explained in man find:

   -exec command ;
          Execute  command;  true  if 0 status is returned.  All following
          arguments to find are taken to be arguments to the command until
          an  argument  consisting of `;' is encountered.  The string `{}'
          is replaced by the current file name being processed  everywhere
          it occurs in the arguments to the command, not just in arguments
          where it is alone, as in some versions of find.  

   -exec command {} +
          This  variant  of the -exec action runs the specified command on
          the selected files, but the command line is built  by  appending
          each  selected file name at the end; the total number of invoca‐
          tions of the command will  be  much  less  than  the  number  of
          matched  files.   
10

In addition to terdon’s answer,

  • “Obviously” -exec … must be terminated with either a semicolon (;) or a plus sign (+).  Semicolon is a special character in the shell (or, at least, every shell I’ve ever used), so, if it is to be used as part of the find command, it must be escaped or quoted (\;, ";", or ';').
  • With -exec … ;, the {} string may appear any number of times in the command, including zero, or two or more, in any position.  See this for an example of why you might want to do -exec without using {}.  Having two or more appearances is useful principally because, in (at least) some versions of find, the {} doesn’t need to be a word by itself; it can have other characters at the beginning or end; e.g.,

    find . -type f -exec mv {} {}.bak ";"
    

    With -exec … +, the {} string must appear as the last argument before the +.  A command like

    find . -name "*.bak" -exec mv {} backup_folder +
    

    results in the enigmatic find: missing argument to ‘-exec’ error message.

    • A workaround for this that’s specific to the cp and mv commands is

      find . -name "*.bak" -exec mv -t backup_folder {} +
      

      or

      find . -name "*.bak" -exec mv --target-directory=backup_folder {} +
      

    The {} must be a word by itself; it cannot have other characters at the beginning or end.  And, in (at least) some versions of find, you may not have more than one {}.

  • A sanity note: You can say

    find . -name "*.sh" -type f -executable -exec {} optional args here ";"

    to run each of your scripts.  But

    find . -name "*.sh" -type f -executable -exec {} +

    runs one of your scripts, with the names of all the others as parameters.  This is similar to saying

    ./*.sh
    

    as a shell command, except find does not guarantee that it sorts its results, so you aren’t guaranteed of running aaa.sh (your alphabetically first *.sh file) as you would be with running ./*.sh.

  • An aspect of find that may not be perfectly clear to beginners is that the command line is, effectively, an executable statement in an arcane language.  For example,

    find . -name "*.sh" -type f -executable -print
    

    means

    for each file
        if the file’s name matches `*.sh` (i.e., if it ends with `.sh`)
        then
            if it is a plain file (i.e., not a directory)
            then
                if it is executable (i.e., the appropriate `---x--x--x` bit is set)
                then
                    print the file’s name
                end if
            end if
        end if
    end loop
    

    or, simply,

    for each file
        if the file’s name matches `*.sh`  AND  it is a plain file  AND  it is executable
        then
            print the file’s name
        end if
    end loop
    

    Some of the - keywords are both an executable action and a test.  In particular, this is true for -exec … ;; for example,

    find . -type f -exec grep -q cat {} ";" -print
    

    translates to

    for each file
        if it is a plain file (i.e., not a directory)
        then
            execute         grep -q cat filename
            if the process succeeds (i.e., exits with status 0)
            then
                print the file’s name
            end if
        end if
    end loop

    which will print the names of all files containing the string “cat”.  And, while this is something that grep can do by itself (with the -l (lower-case L) option), it can be useful to use it with find to find files that contain a certain string AND have a certain size AND are owned by a certain owner AND were modified in a certain time range, ….

    However, this does not work for -exec … +.  Since -exec … + executes one command for multiple files, it doesn’t make sense to use it as a logical condition inside a for each file … loop.

  • The flip side of the above is that find generally exits with an exit status of 0 unless you give it invalid arguments or it encounters a directory that it can’t read.  Even if a program that you execute fails (exits with a non-zero exit status), find will exit with an exit status of 0.  Except if a program that you execute with -exec … + fails (exits with a non-zero exit status), find will exit with a non-zero exit status.

In addition to a million versions of find(1) and testing what find actually does on a couple of systems, The Open Group Base Specifications Issue 7, 2013 Edition provided some of the information on what find must, may, and must not do.

  • 2
    Beware that your example using ... -exec mv {} {}.bak ... is not guranteed to work as expected with all find implementations. The POSIX standard states {} must appear alone to be always recognized, otherwise the behavior is free to keep the characters unchanged or to replace them by the pathname. In the former case, your whole command will essentially delete all files but the last one found... – jlliagre Jun 11 '16 at 0:57

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.