If I want to find any files with .txt in the name, and for every match that is found copy it into the /junk folder the following could be considered to work;

find / -name "*.txt" | cp /junk

but this will not work because find will produce the results as a list, which can’t be fed into cp.

So I could use xargs to resolve this

find / -name "*.txt" | xargs cp /junk

Because xargs will change the list into one line which can be fed into cp. I also think that this command would completely finish searching through the files, 'produce the list', and only then would it run the right side of the pipe (the copy).

Alternatively (and preferably) I could use exec

find / -name ".txt" -exec cp {} /junk \;

Which would run the copy each time that a match is found, (and it would run the copy in a separate process, so the find command would continue running in parallel).

Can someone confirm that these commands and my understanding is correct?

I should add that this is for education purposes only, this is not a real life scenario.

  • One big warning... Your find / -command will include the /junk directory, so files already copied will be copied again. Also, it's probably a good idea to add -i or --backup=numbered to the cp-command. Commented Jan 31, 2017 at 21:33
  • I see, thanks, very interesting point. This is only the case for the third command though correct? Because the second command (providing that I run it only once) would only copy once the find is fully complete?
    – microscope
    Commented Jan 31, 2017 at 21:40
  • No xargs might decide to run the command several times and also before the pipe is finished. Try while sleep 1; do echo foo; done | xargs -n 3.
    – Lucas
    Commented Jan 31, 2017 at 21:45

1 Answer 1

  1. Yes, find / -name "*.txt" | cp /junk will not work because cp does not read its arguments from stdin.
  2. No, find / -name "*.txt" | xargs cp /junk will also not work because xargs will append the arguments it reads from stdin to the command, so cp would try to copy in the wrong direction. (You can try to fix that with the -I option to xargs but then you can simply use find -exec.)
  3. Yes, find / -name ".txt" -exec cp {} /junk \; will work but will use one invocation of cp for each file (and will start that command as soon as the file is found).

You can also try this solution, I should start only one invocation of cp (or at least it will start as few as possible, command line length is limited):

find / -path /junk -prune -or -name '*.txt' -print0 | xargs -0 sh -c 'cp "$@" /junk'

As @don_chrissti commented you can use cp -t if you have GNU cp. It then can be done like this

find / -path /junk -prune -or -name '*.txt' -print0 | xargs -0 cp -t /junk

or even

find / -path /junk -prune -or -name '*.txt' -exec cp -t /junk {} +

All three will collect as many arguments as fit on the command line and execute as few instances of cp as possible. But it might still be the case that a cp is started before find is done so we have to exclude /junk otherwise it might find the copies.

  • Thank you very much Lucas :) I am a little unclear on point 2. So xargs will append the output of the find command as the second argument of the cp command (cp /junk example.txt) which obviously would not work! Now you mention it, I can see that in cmd 2 cp only has one argument which is a problem that I did not see before.
    – microscope
    Commented Jan 31, 2017 at 21:57
  • I guess that {} also needs escaping in some shells (e.g. fish). Commented Feb 1, 2017 at 9:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .