8

I've learned to use the find command by itself to find files and directories however when it comes to doing something with the files/folders I'm confused.

Given the following command:

find . -type f -iname "*.cr2" 

If I want to copy those files found above to a new directory I would naturally think use copy (cp) and pipe |.

find . -type f -iname "*.cr2" | cp \destination_directory

And to me this would get all my photos across an entire drive and nested in levels of sub folders into a single folder ready to start organising.

However people keep telling me to use the -exec parameter, so my question is how do you know when to pipe | and when to use -exec command as below?

 find . -name "*.pdf" -type f -exec cp {} ./pdfsfolder \;

EDIT

The suggested solution (below) only copies files where the file names are unique. and it says cp will not copy file.txt and replace with file.txt. In the instance you copy a lot of files, and don't know if there will be files of the same name, how do you copy something and say rename it if the filename exists?

Suggested solution

find . -type f -iname "*.txt" -print0 | xargs -0 cp -t /home/josh/Documents/copy/

/home/josh/documents/copy being the directory I want to move stuff to.

  • 1
    http://stackoverflow.com/questions/26234041/is-it-possible-to-pipe-the-results-of-find-to-a-copy-command-cp –  Oct 07 '14 at 10:49

1 Answers1

12

There are errors in your assumptions, but first some background:

You should discern two uses of -exec:

  • with \; the {} will be replaced by a single item found
  • with + the {} will be replaced by many items (as many as the commandline can hold).

Therefore your example of -exec use invokes as many cp command as items found by find.

Using find ... -exec cmd {} ... + is similar in efficiency as piping the output of find into a command that handles multiple input names.

You should also take into account that -exec nicely handles filenames/paths with spaces, but when you pipe in the output from find to another program that can cause problems. Therefore some programs that expect a list of filenames from stdin can be given an option to have those filenames NUL separated (often -0 or --null). And find has and option to give them to the next program in that way by specifying: -print0


Now coming to your examples:

find . -type f -iname "*.cr2" | cp destination_directory

doesn't copy the files found, as cp doesn't read from standard input. You would have to use:

find . -type f -iname "*.cr2" | xargs cp -t destination_directory

or to handle the paths with spaces:

find . -type f -iname "*.cr2" -print0 | xargs -0 cp -t destination_directory

With about the same efficiency you could do:

find . -type f -iname "*.cr2" -exec cp -t destination_directory {} +

(As G-Man pointed out the {} has to be at the end, before the +) All of the above do not build the hierarchy under the target directory for that and out of long habit even if the source directory is flat, I find myself using cpio instead:

find . -type f -iname "*.cr2" -print0 | cpio -pdmv0 destination_directory

which nicely lists what it is doing along the way.


The relevant parts from 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.
       Both of these constructions might need to be  escaped  (with  a
       `\')  or  quoted  to  protect them from expansion by the shell.
       See the EXAMPLES section for examples of the use of  the  -exec
       option.   The  specified  command  is run once for each matched
       file.  The command  is  executed  in  the  starting  directory.
       There  are unavoidable security problems surrounding use of the
       -exec action; you should use the -execdir option instead.

-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.  The command line is built in much the same  way
       that xargs builds its command lines.  Only one instance of `{}'
       is allowed within the command.  The command is executed in  the
       starting directory.
phk
  • 5,953
  • 7
  • 42
  • 71
Anthon
  • 79,293
  • 1
    Your fourth example is clearly missing an -exec. And, while I can’t point to any documentation of this, it’s my experience that -exec command … + must have {} as the last argument. So, rather than saying -exec cmd {} … +, you should say -exec cmd … {} +, and your fourth example should be find . -type f -iname "*.cr2" cp -t destination_directory {} +. – G-Man Says 'Reinstate Monica' Oct 07 '14 at 17:07
  • 1
    @G-Man I updated my answer. After trying things out first, and confirming that you are right. I never much use -exec, normally going with xargs or gnu parallel hence I had never noticed. – Anthon Oct 07 '14 at 17:24