Copy Files and Rename in Python

Recently, I was working on a data migration project where I needed to copy thousands of customer invoices from one directory to another while renaming them with a standardized format.

The challenge was that the original files had inconsistent naming conventions, and I needed to organize them systematically. This is a common scenario in Python development, and I’ll show you exactly how to handle it.

In this article, I’ll cover multiple methods to copy files and rename them simultaneously in Python. We’ll explore practical examples that you can use in your projects.

File Operations in Python

Before we start copying and renaming files, it’s important to understand the different modules Python offers for file operations.

Python provides several built-in modules for file manipulation: os, shutil, and pathlib. Each has its strengths depending on your specific needs.

The shutil module is particularly useful for high-level file operations, while os provides lower-level system interface functions.

Method 1: Use shutil.copy() with os.rename()

This is the simple approach that I frequently use in my projects. We first copy the file to the destination, then rename it.

Let me show you how this works with a practical example:

import shutil
import os

def copy_and_rename_basic(source_file, destination_dir, new_name):
    """
    Copy a file to destination directory and rename it

    Args:
        source_file (str): Path to the source file
        destination_dir (str): Destination directory path
        new_name (str): New name for the file
    """
    try:
        # Ensure destination directory exists
        os.makedirs(destination_dir, exist_ok=True)

        # Copy file to destination
        temp_path = shutil.copy(source_file, destination_dir)

        # Create new file path with desired name
        new_path = os.path.join(destination_dir, new_name)

        # Rename the copied file
        os.rename(temp_path, new_path)

        print(f"Successfully copied and renamed: {source_file} -> {new_path}")
        return new_path

    except FileNotFoundError:
        print(f"Error: Source file {source_file} not found")
    except PermissionError:
        print(f"Error: Permission denied when copying {source_file}")
    except Exception as e:
        print(f"Unexpected error: {e}")

# Example usage
source_file = "C:/Users/Documents/report_2024_jan.pdf"
destination_dir = "C:/Users/Documents/organized_reports"
new_name = "january_2024_financial_report.pdf"

copy_and_rename_basic(source_file, destination_dir, new_name)

You can see the output in the screenshot below.

python copy file and rename

This method works well for single-file operations. However, there’s a more elegant approach using direct path manipulation.

Here’s an improved version that combines the copy and rename in one step:

import shutil
import os

def copy_with_new_name(source_file, destination_path):
    """
    Copy file directly to new location with new name

    Args:
        source_file (str): Path to source file
        destination_path (str): Full path including new filename
    """
    try:
        # Ensure destination directory exists
        destination_dir = os.path.dirname(destination_path)
        os.makedirs(destination_dir, exist_ok=True)

        # Copy file directly with new name
        shutil.copy(source_file, destination_path)

        print(f"File copied and renamed: {destination_path}")
        return destination_path

    except Exception as e:
        print(f"Error copying file: {e}")
        return None

# Example: Copy employee handbook with new name
source = "C:/Company/Documents/handbook_v1.pdf"
destination = "C:/Company/Employee_Resources/employee_handbook_2024.pdf"

copy_with_new_name(source, destination)

Method 2: Use shutil.copy2() with Enhanced Metadata Preservation

When working with important files, I often need to preserve metadata like timestamps and file permissions. The shutil.copy2() function is perfect for this.

This method maintains the original file’s metadata while allowing us to rename it:

import shutil
import os
from datetime import datetime

def copy_and_rename_with_metadata(source_file, destination_dir, new_name):
    """
    Copy file with metadata preservation and rename

    Args:
        source_file (str): Source file path
        destination_dir (str): Destination directory
        new_name (str): New filename
    """
    try:
        # Create destination directory if it doesn't exist
        os.makedirs(destination_dir, exist_ok=True)

        # Full destination path
        destination_path = os.path.join(destination_dir, new_name)

        # Copy with metadata preservation
        shutil.copy2(source_file, destination_path)

        # Get file statistics to verify metadata preservation
        src_stat = os.stat(source_file)
        dst_stat = os.stat(destination_path)

        print(f"File copied and renamed successfully!")
        print(f"Source: {source_file}")
        print(f"Destination: {destination_path}")
        print(f"Original modified time: {datetime.fromtimestamp(src_stat.st_mtime)}")
        print(f"New file modified time: {datetime.fromtimestamp(dst_stat.st_mtime)}")

        return destination_path

    except Exception as e:
        print(f"Error during copy operation: {e}")
        return None

# Example: Backup important tax documents
source_file = "C:/Users/Documents/TaxReturns/return_2023.pdf"
backup_dir = "C:/Users/Documents/TaxBackups/2024"
new_name = "tax_return_2023_backup.pdf"

copy_and_rename_with_metadata(source_file, backup_dir, new_name)

You can see the output in the screenshot below.

python copy file with new name

For scenarios where you need to copy multiple files with pattern-based renaming:

import shutil
import os
import glob
from datetime import datetime

def batch_copy_and_rename(source_pattern, destination_dir, name_template):
    """
    Copy multiple files matching pattern and rename them

    Args:
        source_pattern (str): Glob pattern for source files
        destination_dir (str): Destination directory
        name_template (str): Template for new names (use {index} for numbering)
    """
    try:
        # Find all files matching pattern
        source_files = glob.glob(source_pattern)

        if not source_files:
            print("No files found matching the pattern")
            return []

        # Create destination directory
        os.makedirs(destination_dir, exist_ok=True)

        copied_files = []

        for index, source_file in enumerate(source_files, 1):
            # Extract file extension
            _, ext = os.path.splitext(source_file)

            # Generate new filename
            new_name = name_template.format(index=index) + ext
            destination_path = os.path.join(destination_dir, new_name)

            # Copy with metadata
            shutil.copy2(source_file, destination_path)
            copied_files.append(destination_path)

            print(f"Copied: {os.path.basename(source_file)} -> {new_name}")

        print(f"Successfully copied {len(copied_files)} files")
        return copied_files

    except Exception as e:
        print(f"Error in batch operation: {e}")
        return []

# Example: Copy all invoice PDFs and rename them systematically
source_pattern = "C:/Business/Invoices/*.pdf"
destination_dir = "C:/Business/Organized_Invoices"
name_template = "invoice_{index:03d}"  # Creates invoice_001, invoice_002, etc.

batch_copy_and_rename(source_pattern, destination_dir, name_template)

Method 3: Use pathlib for Modern Python Development

The pathlib module, introduced in Python 3.4, provides an object-oriented approach to file system operations. I find it more intuitive and readable.

Here’s how to copy and rename files using pathlib:

from pathlib import Path
import shutil

def copy_and_rename_pathlib(source_path, destination_dir, new_name):
    """
    Copy and rename files using pathlib (Python 3.4+)
    
    Args:
        source_path (str or Path): Source file path
        destination_dir (str or Path): Destination directory
        new_name (str): New filename
    """
    try:
        # Convert to Path objects
        source = Path(source_path)
        dest_dir = Path(destination_dir)
        
        # Check if source file exists
        if not source.exists():
            print(f"Source file does not exist: {source}")
            return None
        
        # Create destination directory if needed
        dest_dir.mkdir(parents=True, exist_ok=True)
        
        # Create full destination path
        destination = dest_dir / new_name
        
        # Copy file
        shutil.copy2(str(source), str(destination))
        
        print(f"File copied successfully!")
        print(f"From: {source}")
        print(f"To: {destination}")
        
        return destination
        
    except Exception as e:
        print(f"Error using pathlib method: {e}")
        return None

# Example: Copy project documentation
source_path = Path("C:/Projects/MyApp/README.md")
destination_dir = Path("C:/Projects/Archive/MyApp_v1.0")
new_name = "project_documentation.md"

copy_and_rename_pathlib(source_path, destination_dir, new_name)

You can see the output in the screenshot below.

python copy and rename file

For more advanced pathlib operations, here’s a function that handles multiple file extensions and provides detailed feedback:

from pathlib import Path
import shutil
from datetime import datetime

class FileCopyRename:
    """Class to handle file copy and rename operations using pathlib"""
    
    def __init__(self, source_dir, destination_dir):
        self.source_dir = Path(source_dir)
        self.destination_dir = Path(destination_dir)
        self.destination_dir.mkdir(parents=True, exist_ok=True)
    
    def copy_with_timestamp(self, filename, new_base_name=None):
        """
        Copy file and add timestamp to name
        
        Args:
            filename (str): Original filename
            new_base_name (str): New base name (optional)
        """
        try:
            source_file = self.source_dir / filename
            
            if not source_file.exists():
                print(f"File not found: {source_file}")
                return None
            
            # Generate timestamp
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            
            # Determine new filename
            if new_base_name:
                new_name = f"{new_base_name}_{timestamp}{source_file.suffix}"
            else:
                new_name = f"{source_file.stem}_{timestamp}{source_file.suffix}"
            
            destination_file = self.destination_dir / new_name
            
            # Copy file
            shutil.copy2(str(source_file), str(destination_file))
            
            print(f"File copied with timestamp: {new_name}")
            return destination_file
            
        except Exception as e:
            print(f"Error copying file with timestamp: {e}")
            return None
    
    def copy_multiple_files(self, file_pattern, name_prefix="file"):
        """
        Copy multiple files matching a pattern
        
        Args:
            file_pattern (str): Glob pattern to match files
            name_prefix (str): Prefix for renamed files
        """
        try:
            matching_files = list(self.source_dir.glob(file_pattern))
            
            if not matching_files:
                print(f"No files found matching pattern: {file_pattern}")
                return []
            
            copied_files = []
            
            for index, source_file in enumerate(matching_files, 1):
                # Create new filename with index
                new_name = f"{name_prefix}_{index:03d}{source_file.suffix}"
                destination_file = self.destination_dir / new_name
                
                # Copy file
                shutil.copy2(str(source_file), str(destination_file))
                copied_files.append(destination_file)
                
                print(f"Copied: {source_file.name} -> {new_name}")
            
            print(f"Successfully copied {len(copied_files)} files")
            return copied_files
            
        except Exception as e:
            print(f"Error in bulk copy operation: {e}")
            return []

# Example: Copy customer data files with organized naming
file_manager = FileCopyRename(
    source_dir="C:/Business/CustomerData/Raw", 
    destination_dir="C:/Business/CustomerData/Processed"
)

# Copy single file with timestamp
file_manager.copy_with_timestamp("customer_survey.xlsx", "survey_results")

# Copy multiple CSV files with systematic naming
file_manager.copy_multiple_files("*.csv", "customer_data")

The pathlib approach offers several advantages over traditional string-based path operations. It’s more readable, handles different operating systems automatically, and provides better error handling.

I particularly like using pathlib when working with complex directory structures because the / operator makes path joining much cleaner than os.path.join().

You can also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.