Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm trying to run a command similar to the one below in a bash script. It should search through all subfolders of $sourcedir and copy all files of a certain type to the root level of $targetdir.

#!/bin/bash

# These are set as arguments to the script, not hard-coded
sourcedir="/path/to/sourcedir"
targetdir="/path/to/targetdir"

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2/`basename "$1"`"' "{}" "$targetdir" \;

This seems to be pretty close, except that {} isn't being passed in as $2 to "-exec sh -c ..."

I would like to do this as close to the "right way" as possible, with tolerance for special characters in filenames (specifically single quote characters).

Thanks in advance!

Edit: I see people suggesting using xargs or argument chaining. I was under the impression that this is only ok for a limited number of arguments. If I have, for example, thousands of .jpg files I'm trying to copy from a number of gallery directories into a giant slideshow directory, will the solutions chaining arguments still work?

Edit 2: My problem was I was missing a _ before my first option to sh in the -exec command. For anyone who is curious about how to make the find command work, add the _ and everything will be fine:

find "$sourcedir" -type f -name "*.type" -exec sh -c 'cp "$1" "$2"' _ "{}" "$targetdir" \;

I have accepted an answer below though because it accomplishes the same task, but is more efficient and elegant.

share|improve this question
4  
That is exactly why xargs was ever created, to automatically handle huge amounts of arguments on regular commands that had limits. Also to consider, most of the max argument limits, have been vastly improved for the standard GNU utils. You also will see a performance benefit, avoiding all those process forks, which on thousands of files is relevant. –  TechZilla Feb 2 '12 at 20:08
    
With gnu-find and + instead of ";", you can handle multiple arguments at once with find too. And you save the complicated argument passing with -print0. –  user unknown Feb 2 '12 at 23:45
    
@userunknown: I'm responding to this below your answer. –  TechZilla Feb 3 '12 at 0:32
add comment

3 Answers

up vote 5 down vote accepted

You want to copy files of a specific type, to a specific directory? This is best done with xargs, and you don't even need that sh. This is a more appropriate way to do it, should also run more efficiently.

find "$sourcedir" -type f -name "*.type" | xargs cp -t targetdir

If you need to handle special file names, then use NULL as your seperator

find "$sourcedir" -type f -name "*.type" -print0 | xargs -0 cp -t "$targetdir"
share|improve this answer
    
For the 2nd case, don't forget to add -print0 to find and -0 to xargs –  SiegeX Feb 2 '12 at 19:58
    
@ SiegeX, already was doing that, before i noticed your comment. –  TechZilla Feb 2 '12 at 20:01
    
Also, NULL is not necessary if you use '{}', its important when you don't. The real benefit between one of the other, other than POSIX compliance, is performance. –  TechZilla Feb 2 '12 at 20:03
add comment

You need to pass {} as an argument to the shell then loop over each arg.

find "$sourcedir" -type f -name "*.type" -exec sh -c 'for f; do cp "$f" "$0"; done' "$targetdir" {} +

Note: The way this works is that the first arg to a shell is the name of the shell, we can exploit this by passing the name as the $targetdir and then use the special parameter $0 inside the shell script to access that targetdir.

share|improve this answer
    
"$targetdir" is not expanded inside single quotes. –  enzotib Feb 2 '12 at 19:31
    
@enzotib good catch. Fixed –  SiegeX Feb 2 '12 at 19:40
add comment

If you don't believe in the church of xargs:

find "$sourcedir" -type f -name "*.mp3" -exec cp -t "$targetdir" {} +

Explanation:

cp -t a b c d 

copies b, c and d to the target dir a.

-exec cmd {} +

invokes the command on a big bunch of files at once, not one after the other (which is standard, if you use ";" instead of +) . This is why have to pull the targetdir to the front and mark it explicitly as target.

This works for gnu-find and might not for other implementations of find. Of course it relies on the -t -flag too.

TechZilla is of course in so far right, as sh isn't needed to invoke cp.

If you don't use xargs, which is most of the time needless in combination with find, you're saved from learning the -print0 and -0 flag.

share|improve this answer
    
Obvisouly, which method anyone might prefer is somwhat subjective. With that said, there does exist reasons to still use xargs. Biggest example, what if you are not finding files? I use xargs in place of general for loops all the time, find is much smaller in scope. In addition, find only supports that + if you are using GNU find, it's not defined in POSIX. So while you should feel free to prefer find alone, It does not do everything xargs does. When you consider GNU xargs you also get -P which does multi-core. Its worth learning xargs regardless. –  TechZilla Feb 3 '12 at 0:38
    
Subjectivity speaking, my opinion obviously does differ. Objectively on the other hand, your answer is also correct. It is one of the few best solutions available. –  TechZilla Feb 3 '12 at 0:43
    
@TechZilla: I hope to remember to revisit this site, when find starts supporting parallel invocation. :) In most cases with copying/moving, disk speed will be the limiting factor, but SSDs might change the picture. You're right, that a non-GNU-solution to provide is a fine thing. Else, the find-solution is objectively shorter, simpler, and uses only two processes. –  user unknown Feb 3 '12 at 1:06
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.