Python
File Handling
Directory Operations
shutil
Programming Tutorial

How do I copy an entire directory of files into an existing directory using Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The easiest way to copy a whole directory tree in Python is shutil.copytree. In modern Python, you can copy into an already existing destination by using dirs_exist_ok=True, which avoids the old need to manually iterate over files just to merge directories.

Copy the source directory into an existing parent directory

If you want source/ to appear as a subdirectory inside an existing destination directory, build the final path explicitly:

python
1from pathlib import Path
2import shutil
3
4source = Path("/tmp/source")
5destination_parent = Path("/tmp/archive")
6
7shutil.copytree(source, destination_parent / source.name, dirs_exist_ok=True)

If source.name is "source", the result is /tmp/archive/source/....

Merge the contents directly into an existing directory

Sometimes you do not want the extra nested directory. You want the contents of source/ copied straight into the existing destination:

python
1from pathlib import Path
2import shutil
3
4source = Path("/tmp/source")
5destination = Path("/tmp/archive")
6
7shutil.copytree(source, destination, dirs_exist_ok=True)

With dirs_exist_ok=True, Python copies files and subdirectories into destination and reuses the directory if it already exists.

What copytree preserves

By default, copytree uses copy2 for files, which means it copies the file contents and attempts to preserve metadata such as modification time. That makes it a better default than a manual loop with copy, especially for backup or packaging tasks.

You can also customize the copy behavior:

python
1import shutil
2
3shutil.copytree(
4    "/tmp/source",
5    "/tmp/archive",
6    dirs_exist_ok=True,
7    ignore=shutil.ignore_patterns("*.pyc", "__pycache__"),
8)

That example skips Python bytecode files while copying everything else.

A fallback for older Python versions

dirs_exist_ok=True was added in Python 3.8. If you are stuck on an older version, you need to walk the source tree yourself and copy entries one by one:

python
1from pathlib import Path
2import shutil
3
4source = Path("/tmp/source")
5destination = Path("/tmp/archive")
6
7for item in source.iterdir():
8    target = destination / item.name
9    if item.is_dir():
10        shutil.copytree(item, target)
11    else:
12        shutil.copy2(item, target)

That works, but it is more verbose and easier to get wrong when nested directories become complex.

Overwrite behavior and safety

When dirs_exist_ok=True is enabled, existing files may be overwritten if the source contains a file with the same relative path. That is often exactly what you want, but it should be a deliberate decision.

If overwriting would be risky, preflight the copy first:

python
1from pathlib import Path
2
3source = Path("/tmp/source")
4destination = Path("/tmp/archive")
5
6conflicts = [
7    destination / path.relative_to(source)
8    for path in source.rglob("*")
9    if path.is_file() and (destination / path.relative_to(source)).exists()
10]
11
12print(conflicts)

That gives you a list of destination files that would be replaced.

You can also validate the source and destination early:

python
1if not source.is_dir():
2    raise ValueError("Source must be an existing directory")
3
4destination.mkdir(parents=True, exist_ok=True)

Those small checks make batch copy utilities much more predictable.

Common Pitfalls

The biggest mistake is confusing "copy this directory into another directory" with "merge this directory's contents into another directory." Those are different operations, and the destination path you pass to copytree determines which one happens.

Another common issue is using copytree without dirs_exist_ok=True on an existing destination. In that case, Python raises FileExistsError.

Be careful with symbolic links and permissions if the directory tree comes from outside your control. Depending on the platform and options, copied results may not behave exactly like the original tree.

Finally, do not fall back to shell commands unless you truly need platform-specific behavior. shutil.copytree is portable, readable, and usually enough.

Summary

  • Use shutil.copytree for recursive directory copies in Python.
  • Pass dirs_exist_ok=True when the destination already exists.
  • Copy to destination / source.name if you want a nested directory, or to destination if you want to merge contents directly.
  • Use ignore= or a preflight conflict check when you need more control.
  • For Python older than 3.8, copy items manually as a compatibility fallback.

Course illustration
Course illustration

All Rights Reserved.