189

Under unix, I want to copy all files with a certain extension (all excel files) from all subdirectories to another directory. I have the following command:

cp --parents `find -name \*.xls*` /target_directory/

The problems with this command are:

  • It copies the directory structure as well, and I only want the files (so all files should end up in /target_directory/)

  • It does not copy files with spaces in the filenames (which are quite a few)

Any solutions for these problems?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Abdel
  • 5,826
  • 12
  • 56
  • 77

7 Answers7

284

--parents is copying the directory structure, so you should get rid of that.

The way you've written this, the find executes, and the output is put onto the command line such that cp can't distinguish between the spaces separating the filenames, and the spaces within the filename. It's better to do something like

$ find . -name \*.xls -exec cp {} newDir \;

in which cp is executed for each filename that find finds, and passed the filename correctly. Here's more info on this technique.

Instead of all the above, you could use zsh and simply type

$ cp **/*.xls target_directory

zsh can expand wildcards to include subdirectories and makes this sort of thing very easy.

Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • Sorry for my bash illiteracy, but what do you mean by "bin"? – Abdel Mar 25 '13 at 14:11
  • 2
    Yes. 'bin it' means to throw it away. Now amended :-) – Brian Agnew Mar 25 '13 at 14:12
  • Haha got it! Tnx! It doesn't quite work for me yet unfortunately... The "cp **/*.xls target_directory" for some reason does not copy all the files (it only looks 1 subdirectory deep, and does not copy files in subdirectories of subdirectories). The "find . -name \*.xls* -exec cp ..." command gives me the message "find: missing argument to `-exec'" – Abdel Mar 25 '13 at 14:22
  • 17
    `Bash` 4.0+ and `ksh93` also supports `**`. For bash, use `shopt -s globstar` to enable it. For ksh, it's `set -G` or `set -o globstar`. – pynexj Mar 25 '13 at 14:58
  • How would you do this in bash 3.2? – Kevin Suttle Jul 17 '15 at 21:14
  • 6
    That exec is technically less efficient than passing into xargs, which will do it all in as few cp calls as possible: `find . -name '*.xls' -print0 | xargs -0 cp -t destdir` – Taywee Jul 01 '16 at 16:32
  • 2
    @Taywee - yes. I didn't want to confuse matters, however, and simply focus on the particular issue. A further efficiency measure is always good – Brian Agnew Jul 01 '16 at 16:36
  • 2
    @BrianAgnew Oh, I'm sure, just good to have extra information in the comments for wayward googlers. – Taywee Jul 01 '16 at 17:38
  • Using this `zsh cp` variant for serching within a directory: `cp -pPR **en.lproj/*.strings SignLocalization`. – lal Jun 02 '17 at 00:15
  • @BrianAgnew This copies the files indeed, however it does not retain the directory structure if used recursively. Is there a quick way of making it retain the directory tree from the source? – TheWhitestOfFangs Jan 21 '19 at 19:44
  • Thanks! Wanted to grab all images and this expansion with a regex worked really nicely find -E source -iregex ".*\.(jpg|jpeg|tiff|png|mpeg|mp4|m4v|avi|mov|mts|m2ts|vob)" -exec cp {} destination \; (mac os) – Datbates Apr 03 '19 at 17:03
  • note that the destination directory must already exist, otherwise this script will simply copy and rename the file to the directory name you supply. – Ivankovich Jul 24 '20 at 07:00
55

From all of the above, I came up with this version. This version also works for me in the mac recovery terminal.

find ./ -name '*.xsl' -exec cp -prv '{}' '/path/to/targetDir/' ';'

It will look in the current directory and recursively in all of the sub directories for files with the xsl extension. It will copy them all to the target directory.

cp flags are:

  • p - preserve attributes of the file
  • r - recursive
  • v - verbose (shows you whats being copied)
guya
  • 5,067
  • 1
  • 35
  • 28
12

I had a similar problem. I solved it using:

find dir_name '*.mp3' -exec cp -vuni '{}' "../dest_dir" ";"

The '{}' and ";" executes the copy on each file.

fhdrsdg
  • 10,297
  • 2
  • 41
  • 62
stingMantis
  • 300
  • 3
  • 10
6

I also had to do this myself. I did it via the --parents argument for cp:

find SOURCEPATH -name filename*.txt -exec cp --parents {} DESTPATH \;
That Guy
  • 61
  • 1
  • 1
5

In 2022 the zsh solution also works in Linux Bash:

cp **/*.extension /dest/dir

works as expected.

2
find [SOURCEPATH] -type f -name '[PATTERN]' | 
    while read P; do cp --parents "$P" [DEST]; done

you may remove the --parents but there is a risk of collision if multiple files bear the same name.

Camion
  • 1,264
  • 9
  • 22
0

On macOS Ventura 13.1, on zsh, I saw the following error when there were too many files to copy, saw the following error:

zsh: argument list too long: cp

Had to use find command along with cp to get the files copied to my destination:

find ./module/*/src -name \*.java -print | while read filelocation; do cp $filelocation mydestinationlocation; done
James Jithin
  • 10,183
  • 5
  • 36
  • 51