Copy and Move Files with Python shutil

Published on: May 19, 2026
Reading time: 6 minutes
Copiando e movendo arquivos com Python usando shutil

Have you ever needed to organize hundreds of files into specific folders and given up because of the manual work involved? Automating file management is one of the most practical entry points into Python automation. Python ships with a powerful built-in module called shutil (short for “shell utilities”) that handles copying, moving, and removing files with a single line of code. This guide gets you through your first working file management script in under two minutes, following the best practices used by professional Python developers.

What Is the shutil Module and Why Use It?

The shutil module is part of Python’s standard library. You do not need to install anything. It is available immediately after you install Python on your computer. Its main purpose is to make high-level file and directory operations simple and reliable. While the os module handles basic file system interactions, shutil was specifically designed for operations like copying files with metadata preservation or moving entire directory trees. According to the official Python shutil documentation, it abstracts platform-specific behavior so your scripts work identically on Windows, macOS, and Linux.

Imagine building an automatic backup system with Python. You need to guarantee that copying a file preserves its read and write timestamps intact. The shutil module provides specific functions like copy2() that do exactly this, which is vital for data integrity in professional environments.

Setting Up: Importing the Module

Since shutil is part of the standard library, setup is just one import at the top of your script. You will typically use it alongside the Python os module for path operations:

import shutil
import os

Copying Files with shutil.copy()

The most basic function for duplicating a file is shutil.copy(). It takes two arguments: the source path and the destination. The destination can be either a folder path (which preserves the original filename) or a full file path (which lets you rename the copy at the same time):

# Copy a file to a new folder (keeps original name)
shutil.copy('project.txt', 'backup/')

# Copy and rename at the same time
shutil.copy('project.txt', 'backup/project_final.txt')

Note that shutil.copy() copies the file content and permissions but does not preserve metadata like the original creation or modification dates. For most everyday automation tasks this is fine, but for backup workflows where timestamps matter, use the function covered in the next section.

Preserving Metadata with shutil.copy2()

When working with data analysis or any workflow where file timestamps must be preserved, shutil.copy2() is the right choice. It works identically to copy() but attempts to copy all associated metadata including the original creation and modification timestamps. This prevents the OS from treating the copy as a brand-new file created today:

# Copy with full metadata preservation
shutil.copy2('sales_report.csv', 'historical_archive.csv')

Moving Files with shutil.move()

Moving a file is a cut-and-paste operation: the file disappears from the source and appears at the destination. The shutil.move() function is intelligent: if the source and destination are on the same disk, it simply renames the path (which is nearly instant). If they are on different disks, it copies the file first and then deletes the original automatically. Using try and except in Python around move operations protects your script from crashing when a file is locked by another process:

# Move a processed log file to an archive folder
shutil.move('logs/error.log', 'processed/error_archived.log')

A common mistake is trying to move a file to a folder that does not yet exist. This raises a FileNotFoundError in Python. Always verify that the destination directory exists, or use os.makedirs() to create it before calling move().

Copying Entire Directories with shutil.copytree()

When you need to copy a whole folder tree including all subdirectories and their contents, shutil.copytree() is the right tool. It is recursive by default, meaning it enters every nested folder and copies everything it finds. An important detail is that the destination folder must not already exist, unless you use the dirs_exist_ok=True parameter available in Python 3.8 and later:

# Copy an entire project folder to an external backup location
shutil.copytree('my_project', '/backup_drive/my_project')

# Python 3.8+: Copy even if destination already exists
shutil.copytree('my_project', '/backup_drive/my_project', dirs_exist_ok=True)

Deleting Directories Safely with shutil.rmtree()

The shutil.rmtree() function permanently deletes an entire folder and all its contents. Use it with extreme caution because the files do not go to the system trash. They are gone immediately. Always validate the path before calling this function to avoid accidentally deleting critical system directories:

# Permanently delete a temporary data folder after processing
shutil.rmtree('temp_data')

shutil Function Comparison Table

FunctionCopies Content?Copies Permissions?Preserves Metadata?Best Use
shutil.copy()YesYesNoQuick file copies
shutil.copy2()YesYesYesBackups and data integrity
shutil.copyfile()YesNoNoContent only (fastest)
shutil.move()N/AYesYesMove or rename files/folders

Handling Errors and Permission Issues

When working with file manipulation, obstacles are inevitable. The most common is a PermissionError in Python, which occurs when the script tries to access a file that is open in another program or a protected system folder. Always wrap your shutil operations in try/except blocks so that if one file fails, the script continues processing the rest rather than stopping completely.

Complete Project Code: Automatic File Organizer

Here is a complete, production-ready script that scans a folder and automatically moves each file into a subdirectory based on its extension. Change the target_folder variable at the bottom to point to your own messy Downloads or Desktop folder:

import shutil
import os

def organize_folder(target_directory):
    """Automatically moves files into subfolders based on their extension."""

    extension_map = {
        'Documents': ['.pdf', '.docx', '.txt', '.pptx'],
        'Images': ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
        'Spreadsheets': ['.csv', '.xlsx', '.xls'],
        'Archives': ['.zip', '.rar', '.7z'],
        'Executables': ['.exe', '.msi']
    }

    if not os.path.exists(target_directory):
        print(f"Error: The folder '{target_directory}' was not found.")
        return

    for filename in os.listdir(target_directory):
        source_path = os.path.join(target_directory, filename)

        # Skip subdirectories, only process files
        if os.path.isdir(source_path):
            continue

        extension = os.path.splitext(filename)[1].lower()

        for folder_name, extensions in extension_map.items():
            if extension in extensions:
                destination_folder = os.path.join(target_directory, folder_name)

                if not os.path.exists(destination_folder):
                    os.makedirs(destination_folder)

                try:
                    shutil.move(source_path, os.path.join(destination_folder, filename))
                    print(f"Moved: {filename} -> {folder_name}")
                except Exception as e:
                    print(f"Error moving {filename}: {e}")

if __name__ == "__main__":
    target_folder = "./my_messy_folder"
    if os.path.exists(target_folder):
        organize_folder(target_folder)
    else:
        print("Target folder not found. Update the path and try again.")

This script is the foundation of more sophisticated automation systems. You can expand it to monitor a folder in real time using the watchdog library, so every newly downloaded file is automatically sorted the moment it lands in your Downloads folder, without you ever running the script manually.

Frequently Asked Questions

What is the difference between shutil.copy() and shutil.copy2()?

copy() copies the file content and permissions but discards metadata like original timestamps. copy2() copies the content, permissions, and attempts to preserve all metadata including the creation and modification dates.

Can shutil.move() rename files at the same time?

Yes. If you provide a destination path with a different filename, Python moves the file and applies the new name simultaneously. It works as a combined move-and-rename operation in a single call.

What happens if the destination of shutil.move() already exists?

If the destination is an existing file, it is overwritten without warning. If it is a directory, the source file is moved inside it. Always validate paths before calling the function to avoid accidental overwrites.

How do I copy a folder that already exists at the destination?

Use shutil.copytree(source, destination, dirs_exist_ok=True) in Python 3.8 or later. This allows copying into an existing directory without raising a FileExistsError.

Does shutil work on Windows, Linux, and macOS?

Yes. The shutil module is fully cross-platform. It abstracts the differences between operating systems, making it ideal for writing portable scripts that run identically across different environments.

Can I track progress when copying large files?

The basic shutil functions do not show progress bars natively. To add one, use the tqdm library alongside a custom copy function that reads and writes in chunks while updating the progress counter.

Does shutil.rmtree() send files to the trash?

No. It permanently deletes the entire directory tree immediately. The files bypass the system trash completely. Use it only when you are absolutely certain the data is no longer needed, and always test on a copy first.

How do I handle very long file paths on Windows?

Windows limits paths to 260 characters by default. To work with longer paths, use the \? prefix in path strings or enable long path support in the Windows registry settings. Using the Python pathlib module also provides cleaner cross-platform path handling that reduces these issues.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Instalação offline de pacotes Python sem internet
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    How to Install Python Packages Offline in Minutes

    Learn how to install Python packages offline with pip download, pip install --no-index, wheels, cross-platform files, Conda, and private mirrors.

    Ler mais

    Tempo de leitura: 9 minutos
    19/05/2026
    Compactando arquivos ZIP automaticamente com Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    How to Create ZIP Files with Python in 2 Minutes

    Learn how to create ZIP files in Python with zipfile, compress folders, filter extensions, handle errors, and build a ready-to-run

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026
    Como acelerar código Python usando lru cache
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Speed Up Python Code with @lru_cache

    Learn how @lru_cache speeds up Python code with memoization, maxsize, cache_info, cache_clear, API caching, and cases where it should be

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026
    Monitoramento de pastas em tempo real com Python Watchdog
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Monitor Folders in Real Time with Python Watchdog

    Learn how to monitor folders in real time with Python Watchdog using Observer, handlers, file filters, permissions, logging, and a

    Ler mais

    Tempo de leitura: 7 minutos
    19/05/2026
    Redimensionamento de imagens com Pillow em Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    How to Resize Images with Pillow in Python

    Learn how to resize images with Python's Pillow library. This guide covers basic resizing, aspect ratio preservation, the thumbnail method,

    Ler mais

    Tempo de leitura: 9 minutos
    12/05/2026