Cp -r with exec of find

Hi,

Using this simple logic to copy only directories and their content to a different location leaving the files , but it does not work as expected . It copies individual files too.

What's wrong in this ? Doesn't the find outputs only directories ?

From a directory containing some sub-directories and files , I need to just copy all sub-directories as it is to SomeOtherLocation

find . -name "*" -type d -exec cp -r {} /SomeOtherLocation/test/{} \;

Even with xargs , it's same problem :
find . -name "*" -type d | xargs -i cp -r {} /SomeOtherLocation/test/{}

Using mkdir -p makes empty directories as it's not a copy command.

Help is appreciated .

You can find out what find selects by replacing -exec (and everything after it) by -ls. It will indeed list only directories.

The issue is that cp -r recursively copies those directories and their contents: it does not know about the type -d, and it does not have any similar option to skip files, sockets etc.

In fact, I think the issue is worse than you are seeing: I would expect cp -r to copy every level of directory multiple times: that is, a tree with a/b/c level directories would recursively copy everything under a,then everything under a/b again, then everything under a/b/c yet again.

I don't have a simple answer to this (at least, not one I want to test on my own Laptop right now). You can collect the directory names from find (maybe using -stat with suitable options), but you are still going to mkdir each of a, a/b and a/b/c . Possibly the -p option to mkdir would be helpful.

You might find that the find -exec "+" operator optimises mkdir somewhat.

There are some find versions where -name "*" behaves like a shell glob, i.e. does not match filenames that start with a dot.
GNU find is different: the start directory "." is matched by the "*".
Solutions with find:

find . -mindepth 1 -type d -prune -exec cp -r -t /SomeOtherLocation/test/ {} +
find . -mindepth 1 -maxdepth 1 -type d -exec cp -r -t /SomeOtherLocation/test/ {} +

-prune or -maxdepth 1 will not further descend and copy again what have already been copied by the cp -r
Solution without find:

cp -r */ /SomeOtherLocation/test/

The trailing / enforces directories. The */ could bail out with "two many arguments", at least in theory.

We seem to have different interpretations of the requirement. I am seeing "copy only directories" (i.e. not their contents) and "It copies individual files too" as being a Bad Thing.

I think the requirement is to set up a new instance of the existing directory tree (directories only), without any of the original files. cp -r is the exact opposite of that.

I considered tar (because that copies the metadata like permissions, ownership, and timestamps), but I see no way to not copy the files too. I thought of copying the whole directory with tar/untar, and then using find to delete everything except directories, but that is something of a sledge-hammer.

My offering for this case is similar to:

Old='/home/paul/Downloads/Volvo'
New='/home/paul/spoom/dTree'
mkdir "${New}"
( cd "${Old}" && find . -type d ) | ( cd "${New}" && xargs -I {} mkdir -p {} )

FWIW, my interpretation of the OPs requirement is to "copy only directories and their content to a different location" but not files at the starting level. So from the starting level, ignore all files, process only directories and their contents below.

I will wait to give the OP an opportunity to provide more detail, hopefully with a specific example of the original and copied directory trees.

I feel that "copy all sub-directories" is intended to mean the directory entries alone, and not any files that they contain, as in ".. leaving the files". The query "Doesn't the find output only directories?" seems to imply they are the only items of interest, and that the results of the cp -r are unexpected.

There is also a serious performance hit within the original solutions (although with a fix from @MadeInGermany). The find recurses, and the cp -r recurses again on those results. So if find locates a/b/c/d/e, then every file in the lowest directory gets copied five times, because it is in the cp -r scope of a, a/b, a/b/c, a/b/c/d, and a/b/c/d/e.

Hello all and thanks for this discussion !.

@Paul_Pedant

Paul, apart from the immediate directory I am in, I needed to leave the individual files, and copy the rest below this level, as below this everything will be a part of some sub-directory. That was it.

I didn't reach that level, so its a further thing.
In first place, by what logic, files in directory I am standing, are not being ignored, when find is correctly filtering only directories. Didn't understand the logic behind this behavior.
Now If you can shed some more light, Paul :slightly_smiling_face:

@hicksd8
You got me right !

I am seeing an interesting discussion here and it would be further of interest with approaches being talked.

BTW, I found a simpler thing with rsync, :

rsync -av -f"-! */" ./ /SomeOtherLocation/test/

And... with ls , its working correctly :

ls -l | grep "^d" | awk -F" " '{print $NF}' | xargs -i cp -r {} /SomeOtherLocation/test/{}

I'm away from base with no access to Unix/Linux system but I would be trying this. Test the file selection is correct with:

# cd <source directory>
# find . -type d -print

If that outputs the wrong selection then modify the 'find' until it does.
If that outputs the right selection then pipe it to cpio:

# find . -type d -print|cpio -puvdm <dest directory>

Note that the destination directory must pre-exist and typically be empty.

That's my two cents

PS. If you want to copy sym links too then include 'l' with the cpio switches.

PPS. Obviously some of this depends on the O/S and shell versions.

Yes, and cp by default will replace a symlink by its target(-file).
But cp can be improved with -p (preserve attributes) and -d (preserve links, in GNU cp)

@MadeInGermany

Oh yes.. this is rather simpler.
I just don't need find in my scenario.
Thanks for this. :slightly_smiling_face:

And this can even be used with find to give correct results wherein I suppose but copy-operation (as per *) is being done with each match of find, which is a separate directory each time. How can I analyze its is happening this way. What your mind and experience say about this!

BTW, The long one with mindepth , I haven't given a look yet.

Paul, IMHO, I suppose it's not the cp -r , but the functionality of find to take each sub-directory as a different match enables to copy multiple times
e.g /A is a match
/A/B is a second match
/A/B/C is third match
and so on.

Thanks hicks for your these 2 cents, they are worth.

Put an echo before the cp
then you'll see how it is invoked!

Discover the difference between \; and + in a find -exec

find . -mindepth 1 -maxdepth 1 -type d -exec echo cp -rpd -t /SomeOtherLocation/test/ {} +

find . -mindepth 1 -maxdepth 1 -type d -exec echo cp -rpd -t /SomeOtherLocation/test/ {} \;

Can somebody explain why the find is copying files of present directory along with directories .

find without cp matches only directories , but with cp, files in present directory are also copied (ignore the recursive effect of find)

find . -name "*" -type d -exec cp -r */ /SomeOtherLocation/ \;

Visualization:
Command executed from Dir A, which has files 1, 2, 3 along with other directories.
Files 1, 2, 3 are also copied.

and cp -r copies it recursively.

The man page for cp is grossly inadequate, especially for the recursive copying.

-R, -r, --recursive
              copy directories recursively

Please read the full documentation here: it has 50 lines of detailed explanation, so you probably need to read it several times.

https://www.gnu.org/savannah-checkouts/gnu/coreutils/manual/html_node/cp-invocation.html

cp is a mess. Even for simple cases, it does different things when the destination does not exist, or exists as a file, or exists as a directory, or has various permission settings.

cp -r is a different (and worse) mess. It refuses to run unless the source is a directory. If it does run, it recurses through a complete subtree creating directories and files (including file types like fifos and sockets), and there are no options that modify or limit its scope.

cp -r will obey its own options, regardless of how much you might dress it up inside find or xargs. Your wrappers merely send it a single directory name, and it will obediently replicate the entire directory/file subtree rooted at that directory.

The only cp option that accepts a variable number of args (as permitted in -exec or xargs) is cp -t, where the target is validated as a directory. And right now, I don't even trust that to behave itself if any of the following source args are directories, or soft links, and/or in conjunction with a -r option.

As of now, I believe the only safe version of cp is cp -t target src1 src2 src3 ..., where I have also previously verified that target is a writeable directory, and each src is a readable regular file.

Section 2.6 Target Directory of the full document (noted above) lists at least four ways that cp -t can fail, or do something other that what your command clearly intended. The same issues arise with the mv command, which pretty much does what cp does, but then deletes the original files so you cannot have another try at getting it right. Frequent backups (like tar on the whole of the original directories) have saved my job more often than I care to admit.

Well..then it simply means failure of -type d on present directory but the rest, when used with cp , without cp it only selects directories.

find invokes cp -r .
And that copies all the files in .

You might want to re-read several of the previous responses in closer detail.

Nothing is failing. The utilities are doing precisely and exactly what you have requested them to do. What you are expecting to happen is some kind of magic connection between two utilities which actually know nothing about each other.

I can explain it for you, but I cannot understand it for you.

You might try the -v (--verbose) option in cp. It asserts that it will "explain what is being done".

This shows one explicit directory name being passed to find -exec.

paul: ~/D_Recovery $ find . -name _SCRIPTS -type d -print
./_SCRIPTS

and this shows what cp -r does when given that single directory name.

paul: ~/D_Recovery $ find . -name _SCRIPTS -type d -exec cp --verbose -r {} ~/spoom/cpTest/{} ';'
'./_SCRIPTS' -> '/home/paul/spoom/cpTest/./_SCRIPTS'
'./_SCRIPTS/Size.V01' -> '/home/paul/spoom/cpTest/./_SCRIPTS/Size.V01'
'./_SCRIPTS/20260731_102710_Chart.txt' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260731_102710_Chart.txt'
'./_SCRIPTS/20260731_102710_Fetch.bash' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260731_102710_Fetch.bash'
'./_SCRIPTS/20260626_105210_Chart.txt' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260626_105210_Chart.txt'
'./_SCRIPTS/20260626_105210_Fetch.bash' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260626_105210_Fetch.bash'
'./_SCRIPTS/20260710_135710_Chart.txt' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260710_135710_Chart.txt'
'./_SCRIPTS/20260710_135710_Fetch.bash' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260710_135710_Fetch.bash'
'./_SCRIPTS/20250213_132310_Chart.txt' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20250213_132310_Chart.txt'
'./_SCRIPTS/20260717_181210_Chart.txt' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260717_181210_Chart.txt'
'./_SCRIPTS/20250213_132310_Fetch.bash' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20250213_132310_Fetch.bash'
'./_SCRIPTS/20260717_181210_Fetch.bash' -> '/home/paul/spoom/cpTest/./_SCRIPTS/20260717_181210_Fetch.bash'