"
This article is part of in the series
Last Updated: Wednesday 29th December 2021

Ever tried to copy a directory/folder in Python? Ever failed? No matter. Try again!

If you haven't already read it, look at the article How to Copy a File in Python with shutil for an explanation of how to copy files with shutil.

Recursively Copying a Directory/Folder of Files in Python

In the article that was mentioned above, we saw how to copy individual files in Python. I think you'll agree that much more useful is the ability to copy entire directories into other directories.

Python's shutil module once again saves our butts and has a function called copytree for doing just what we want.

We don't need that drastic of a change, so I'll modify our code above slightly to be able to copy directories as follows:

[python]
import shutil

def copyDirectory(src, dest):
try:
shutil.copytree(src, dest)
# Directories are the same
except shutil.Error as e:
print('Directory not copied. Error: %s' % e)
# Any error saying that the directory doesn't exist
except OSError as e:
print('Directory not copied. Error: %s' % e)
[/python]

Ok, nice! Almost the same as our function that copies single files, but it only allows to copy directories instead of files and the exceptions thrown are a bit different. Awesome.

Copying Both Files and Directories in Python

But what if we want one solid function that copies both files and directories? No problem! We can write a simple, slightly more complex function for that.

Observe:

[python]
import errno

def copy(src, dest):
try:
shutil.copytree(src, dest)
except OSError as e:
# If the error was caused because the source wasn't a directory
if e.errno == errno.ENOTDIR:
shutil.copy(src, dest)
else:
print('Directory not copied. Error: %s' % e)
[/python]

This function will copy both files and directories. First, we put our copytree function in a try block to catch any nasty exceptions. If our exception was caused because the source directory/folder was actually a file, then we copy the file instead. Simple stuff.

A note to add is that it really isn't possible to actually copy a directory over. You need to recursively walk the directories and create the directory structure based on the old one, and copy the files in each sub-directory to their proper directories at the destination. If you wished to implement this yourself, see this article for an example of how to recursively walk directories in Python.

Ignoring Files and Directories

The function shutil.copytree takes in an argument that allows you to specify a function that returns a list of directories or files that should be ignored.
A simple example function that does this is as follows:
[python]
def ignore_function(ignore):
def _ignore_(path, names):
ignored_names = []
if ignore in names:
ignored_names.append(ignore)
return set(ignored_names)
return _ignore_
[/python]

Alright, what does this function do? Well, we specify some sort of file or directory name as the argument ignore, which acts as the filter for names. If ignore is in names, then we add it to an ignored_names list that specifies to copytree which files or directories to skip.

How would we use this function? See our modified copy function below:

[python]
def copy(src, dest):
try:
shutil.copytree(src, dest, ignore=ignore_function('specificfile.file'))
except OSError as e:
# If the error was caused because the source wasn't a directory
if e.errno == errno.ENOTDIR:
shutil.copy(src, dest)
else:
print('Directory not copied. Error: %s' % e)
[/python]

But what if we want to filter out more than just one file? Luckily, shutil has got us covered again! It's got a function called shutil.ignore_patterns that allows us to specify glob patterns to filter out files and directories. See our once again modified copy function below:

[python]
def copy(src, dest):
try:
shutil.copytree(src, dest, ignore=ignore_patterns('*.py', '*.sh', 'specificfile.file'))
except OSError as e:
# If the error was caused because the source wasn't a directory
if e.errno == errno.ENOTDIR:
shutil.copy(src, dest)
else:
print('Directory not copied. Error: %s' % e)
[/python]

This will ignore all Python files, shell files and our own specific file when copying over the directory. ignore_patterns takes in arguments that specify the patterns to ignore and returns a function that copytree can understand, a lot like how our custom function ignore_function did it, but more robust.

That's all I have to share with you!

Peace.

About The Author