Explain Codes LogoExplain Codes Logo

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

python
file-management
directory-copying
python-utilities
Alex KataevbyAlex Kataev·Oct 18, 2024
TLDR

The shutil and os modules in Python provide means to copy files and directories including their metadata (file attributes), to an existing directory:

import os, shutil def recursive_copy(src, dst): os.makedirs(dst, exist_ok=True) # Who needs an iOS? OSError's banished here. for item in os.listdir(src): source_item = os.path.join(src, item) dest_item = os.path.join(dst, item) if os.path.isdir(source_item): recursive_copy(source_item, dest_item) # Recursion? More like inception! We're going deeper. else: shutil.copy2(source_item, dest_item) # It's Copy2, the file-copier sequel! Now with metadata action. recursive_copy('/source/dir', '/target/existing/dir')

All files and subdirectories in /source/dir will be copied into /target/existing/dir while also preserving the original attributes of the files.

Beyond the basic: Exceptional conditions and Python versions

Workaround for Python < 3.8

OSError likes to crash the party if your Python version < 3.8. The copy_tree function from the distutils.dir_util module can help you handle that:

from distutils.dir_util import copy_tree copy_tree('/source/dir', '/target/existing/dir') # No gate crashing OSError today!

Look out for symlinks and file permissions while copying. You don't want to leave anything behind:

def custom_copy(src, dst): os.makedirs(dst, exist_ok=True) for item in os.listdir(src): s = os.path.join(src, item) d = os.path.join(dst, item) if os.path.islink(s): linkto = os.readlink(s) os.symlink(linkto, d) # Creating a new link to the past os.lchmod(d, os.lstat(s).st_mode) # I find your lack of permissions disturbing elif os.path.isdir(s): custom_copy(s, d) # What's recursion's concept? See line 2 else: shutil.copy2(s, d) # Copy2 strikes back custom_copy('/source/dir', '/target/existing/dir')

Catch errors while they last

Preferred way to handle errors like a pro is to catch the returned shutil.Error:

try: custom_copy('/source/dir', '/target/existing/dir') except shutil.Error as e: print(f'Error: {e}') # Detective Python will crack the Error Case

Various scenarios when copying directories

Selective updates: only fresh gets served

Updating files selectively, like careful chefs adding seasoning to a dish:

import os, shutil, filecmp def selective_copy(src, dst): # If directory doesn't exist or file is fresher than its copy, copy it. `shallow=False` for the chef's touch if not os.path.exists(dst) or not filecmp.cmp(src, dst, shallow=False): shutil.copy2(src, dst)

Use this function instead of shutil.copy2 in recursive_copy.

Strictly subdirectories: Files need not apply

If you don't want to copy top-level files but only subdirectories, filter them out:

from pathlib import Path def dirs_only_copy(src, dst): for item in Path(src).iterdir(): if item.is_dir(): recursive_copy(str(item), os.path.join(dst, item.name)) dirs_only_copy('/source/dir', '/target/existing/dir') # It's the Subdirectories Only Club

Copying and applying file status metadata

For those wanting an exact replica, use shutil.copystat after copying to apply metadata:

import shutil # After executing recursive_copy shutil.copystat('/source/dir', '/target/existing/dir') # It's Twilight Zone time: Mirror Image