How to force 'cp' to overwrite directory instead of creating another one inside?

I'm trying to write a Bash script that will overwrite an existing directory. I have a directory foo/ and I am trying to overwrite bar/ with it. But when I do this:

cp -Rf foo/ bar/

a new bar/foo/ directory is created. I don't want that. There are two files in foo/; a and b. There are files with same names in bar/ as well. I want the foo/a and foo/b to replace bar/a and bar/b.


Solution 1:

You can do this using -T option in cp.
See Man page for cp.

-T, --no-target-directory
    treat DEST as a normal file

So as per your example, following is the file structure.

$ tree test
test
|-- bar
|   |-- a
|   `-- b
`-- foo
    |-- a
    `-- b
2 directories, 4 files

You can see the clear difference when you use -v for Verbose.
When you use just -R option.

$ cp -Rv foo/ bar/
`foo/' -> `bar/foo'
`foo/b' -> `bar/foo/b'
`foo/a' -> `bar/foo/a'
 $ tree
 |-- bar
 |   |-- a
 |   |-- b
 |   `-- foo
 |       |-- a
 |       `-- b
 `-- foo
     |-- a
     `-- b
3 directories, 6 files

When you use the option -T it overwrites the contents, treating the destination like a normal file and not directory.

$ cp -TRv foo/ bar/
`foo/b' -> `bar/b'
`foo/a' -> `bar/a'

$ tree
|-- bar
|   |-- a
|   `-- b
`-- foo
    |-- a
    `-- b
2 directories, 4 files

This should solve your problem.

Solution 2:

If you want to ensure bar/ ends up identical to foo/, use rsync instead:

rsync -a --delete foo/ bar/

If just a few things have changed, this will execute much faster than removing and re-copying the whole directory.

  • -a is 'archive mode', which copies faithfully files in foo/ to bar/
  • --delete removes extra files not in foo/ from bar/ as well, ensuring bar/ ends up identical
  • If you want to see what it's doing, add -vh for verbose and human-readable
  • Note: the slash after foo is required, otherwise rsync will copy foo/ to bar/foo/ rather than overwriting bar/ itself.
    • (Slashes after directories in rsync are confusing; if you're interested, here's the scoop. They tell rsync to refer to the contents of the directory, rather than the directory itself. So to overwrite from the contents of foo/ onto the contents of bar/, we use a slash on both. It's confusing because it won't work as expected with a slash on neither, though; rsync sneakily always interprets the destination path as though it has a slash, even though it honors an absence of a slash on the source path. So we need a slash on the source path to make it match the auto-added slash on the destination path, if we want to copy the contents of foo/ into bar/, rather than the directory foo/ itself landing into bar/ as bar/foo.)

rsync is very powerful and useful, if you're curious look around for what else it can do (such as copying over ssh).

Solution 3:

Do it in two steps.

rm -r bar/
cp -r foo/ bar/