AutomateMyJob
Back to BlogPython Automation

Batch Rename Hundreds of Files in Seconds with Python

Alex Rodriguez10 min read

Batch Rename Hundreds of Files in Seconds with Python

You've got 500 photos from an event named IMG_0001.jpg through IMG_0500.jpg. You need them named company_retreat_2025_001.jpg through company_retreat_2025_500.jpg.

Renaming them one by one? That's 8+ hours of mind-numbing work. A Python script? About 2 seconds.

Let's build a flexible file renaming tool that handles patterns, sequences, date insertion, and more.

What You'll Learn

  • How to safely rename files with Python
  • Building flexible naming patterns
  • Working with regular expressions for advanced renaming
  • Creating dry-run modes to preview changes safely

Prerequisites

  • Python 3.8 or higher
  • No external libraries needed
  • A folder of files you want to rename

The Problem

File renaming is tedious when you need to:

  • Add prefixes or suffixes to many files
  • Replace text patterns across filenames
  • Add sequential numbers
  • Standardize naming conventions
  • Insert dates or other metadata

File Explorer and Finder have basic batch rename features, but they're limited. Python gives you complete control.

The Solution

We'll build a script that supports multiple renaming modes:

  1. Pattern replacement: Replace text in filenames
  2. Prefix/Suffix: Add text to the beginning or end
  3. Sequential numbering: Rename with numbered sequences
  4. Case conversion: Standardize to lowercase, uppercase, or title case

Step 1: Setting Up Safe Renaming

The key to safe renaming is never losing files. We'll always check for conflicts and provide a preview mode:

python
1from pathlib import Path
2
3
4def get_files_in_folder(folder_path, extension_filter=None):
5    """
6    Get all files in a folder, optionally filtered by extension.
7    
8    Args:
9        folder_path: Path to the folder
10        extension_filter: Optional extension like '.jpg' or list ['.jpg', '.png']
11    
12    Returns:
13        List of Path objects for matching files
14    """
15    folder = Path(folder_path)
16    
17    if not folder.exists():
18        raise FileNotFoundError(f"Folder not found: {folder_path}")
19    
20    files = [f for f in folder.iterdir() if f.is_file()]
21    
22    # Filter by extension if specified
23    if extension_filter:
24        if isinstance(extension_filter, str):
25            extension_filter = [extension_filter]
26        
27        # Normalize extensions to lowercase with leading dot
28        extensions = [ext.lower() if ext.startswith('.') else f'.{ext.lower()}' 
29                      for ext in extension_filter]
30        
31        files = [f for f in files if f.suffix.lower() in extensions]
32    
33    return sorted(files)

Step 2: Pattern Replacement

The most common renaming need—find and replace text in filenames:

python
1def rename_replace_pattern(files, find_text, replace_text, case_sensitive=True):
2    """
3    Replace a pattern in all filenames.
4    
5    Args:
6        files: List of Path objects
7        find_text: Text to find
8        replace_text: Text to replace with
9        case_sensitive: Whether to match case
10    
11    Returns:
12        List of tuples: (original_path, new_name)
13    """
14    renames = []
15    
16    for file_path in files:
17        original_name = file_path.stem  # Filename without extension
18        extension = file_path.suffix
19        
20        if case_sensitive:
21            new_name = original_name.replace(find_text, replace_text)
22        else:
23            # Case-insensitive replacement
24            import re
25            pattern = re.compile(re.escape(find_text), re.IGNORECASE)
26            new_name = pattern.sub(replace_text, original_name)
27        
28        # Only include if name actually changed
29        if new_name != original_name:
30            renames.append((file_path, f"{new_name}{extension}"))
31    
32    return renames

Step 3: Sequential Numbering

Rename files with a pattern and sequential numbers:

python
1def rename_sequential(files, pattern, start_number=1, padding=3):
2    """
3    Rename files with sequential numbers.
4    
5    Args:
6        files: List of Path objects
7        pattern: Name pattern with {n} for number placeholder
8                 Example: "photo_{n}" -> "photo_001.jpg"
9        start_number: Starting number for sequence
10        padding: Number of digits (3 means 001, 002, etc.)
11    
12    Returns:
13        List of tuples: (original_path, new_name)
14    """
15    renames = []
16    
17    for i, file_path in enumerate(files):
18        extension = file_path.suffix
19        number = start_number + i
20        
21        # Format number with padding
22        formatted_number = str(number).zfill(padding)
23        
24        # Replace placeholder with number
25        new_name = pattern.replace("{n}", formatted_number)
26        
27        renames.append((file_path, f"{new_name}{extension}"))
28    
29    return renames

Step 4: Prefix and Suffix Operations

Add text to the beginning or end of filenames:

python
1def rename_add_prefix(files, prefix):
2    """Add a prefix to all filenames."""
3    renames = []
4    
5    for file_path in files:
6        new_name = f"{prefix}{file_path.name}"
7        renames.append((file_path, new_name))
8    
9    return renames
10
11
12def rename_add_suffix(files, suffix):
13    """Add a suffix before the extension."""
14    renames = []
15    
16    for file_path in files:
17        new_name = f"{file_path.stem}{suffix}{file_path.suffix}"
18        renames.append((file_path, new_name))
19    
20    return renames

Step 5: Safe Execution with Preview

Before making changes, we should preview them and check for conflicts:

python
1def check_conflicts(renames, folder_path):
2    """
3    Check for naming conflicts.
4    
5    Args:
6        renames: List of (original_path, new_name) tuples
7        folder_path: Folder where files are located
8    
9    Returns:
10        List of conflict descriptions
11    """
12    folder = Path(folder_path)
13    conflicts = []
14    
15    # Track new names to detect duplicates within our rename set
16    new_names = {}
17    
18    for original_path, new_name in renames:
19        # Check if new name already exists in folder
20        new_path = folder / new_name
21        if new_path.exists() and new_path != original_path:
22            conflicts.append(f"'{new_name}' already exists in folder")
23        
24        # Check for duplicates in our rename list
25        if new_name in new_names:
26            conflicts.append(f"Duplicate new name: '{new_name}'")
27        new_names[new_name] = original_path
28    
29    return conflicts
30
31
32def execute_renames(renames, dry_run=True):
33    """
34    Execute the rename operations.
35    
36    Args:
37        renames: List of (original_path, new_name) tuples
38        dry_run: If True, only preview changes without renaming
39    
40    Returns:
41        Number of files renamed
42    """
43    if dry_run:
44        print("\n📋 PREVIEW MODE (no changes will be made)")
45        print("=" * 60)
46        
47        for original_path, new_name in renames:
48            print(f"  {original_path.name}")
49            print(f"    → {new_name}")
50            print()
51        
52        print(f"Total: {len(renames)} file(s) would be renamed")
53        return 0
54    
55    # Execute renames
56    print("\n🔄 RENAMING FILES")
57    print("=" * 60)
58    
59    renamed_count = 0
60    for original_path, new_name in renames:
61        new_path = original_path.parent / new_name
62        
63        try:
64            original_path.rename(new_path)
65            print(f"  ✓ {original_path.name}{new_name}")
66            renamed_count += 1
67        except Exception as e:
68            print(f"  ✗ {original_path.name}: {e}")
69    
70    print(f"\n✅ Renamed {renamed_count} of {len(renames)} files")
71    return renamed_count

The Complete Script

python
1#!/usr/bin/env python3
2"""
3Batch File Renamer - Rename multiple files with patterns and sequences.
4Author: Alex Rodriguez
5
6Supports: pattern replacement, sequential numbering, prefix/suffix,
7and case conversion with dry-run preview mode.
8"""
9
10import re
11from pathlib import Path
12
13
14def get_files_in_folder(folder_path, extension_filter=None):
15    """
16    Get all files in a folder, optionally filtered by extension.
17    
18    Args:
19        folder_path: Path to the folder
20        extension_filter: Optional extension like '.jpg' or list ['.jpg', '.png']
21    
22    Returns:
23        List of Path objects for matching files
24    """
25    folder = Path(folder_path)
26    
27    if not folder.exists():
28        raise FileNotFoundError(f"Folder not found: {folder_path}")
29    
30    files = [f for f in folder.iterdir() if f.is_file()]
31    
32    if extension_filter:
33        if isinstance(extension_filter, str):
34            extension_filter = [extension_filter]
35        
36        extensions = [ext.lower() if ext.startswith('.') else f'.{ext.lower()}' 
37                      for ext in extension_filter]
38        
39        files = [f for f in files if f.suffix.lower() in extensions]
40    
41    return sorted(files)
42
43
44def rename_replace_pattern(files, find_text, replace_text, case_sensitive=True):
45    """Replace a pattern in all filenames."""
46    renames = []
47    
48    for file_path in files:
49        original_name = file_path.stem
50        extension = file_path.suffix
51        
52        if case_sensitive:
53            new_name = original_name.replace(find_text, replace_text)
54        else:
55            pattern = re.compile(re.escape(find_text), re.IGNORECASE)
56            new_name = pattern.sub(replace_text, original_name)
57        
58        if new_name != original_name:
59            renames.append((file_path, f"{new_name}{extension}"))
60    
61    return renames
62
63
64def rename_sequential(files, pattern, start_number=1, padding=3):
65    """
66    Rename files with sequential numbers.
67    
68    Pattern uses {n} as placeholder for the number.
69    Example: "photo_{n}" with padding=3 -> "photo_001.jpg"
70    """
71    renames = []
72    
73    for i, file_path in enumerate(files):
74        extension = file_path.suffix
75        number = start_number + i
76        formatted_number = str(number).zfill(padding)
77        new_name = pattern.replace("{n}", formatted_number)
78        renames.append((file_path, f"{new_name}{extension}"))
79    
80    return renames
81
82
83def rename_add_prefix(files, prefix):
84    """Add a prefix to all filenames."""
85    renames = []
86    for file_path in files:
87        new_name = f"{prefix}{file_path.name}"
88        renames.append((file_path, new_name))
89    return renames
90
91
92def rename_add_suffix(files, suffix):
93    """Add a suffix before the extension."""
94    renames = []
95    for file_path in files:
96        new_name = f"{file_path.stem}{suffix}{file_path.suffix}"
97        renames.append((file_path, new_name))
98    return renames
99
100
101def rename_change_case(files, case_type):
102    """
103    Change filename case.
104    
105    Args:
106        files: List of Path objects
107        case_type: 'lower', 'upper', or 'title'
108    """
109    renames = []
110    
111    for file_path in files:
112        original_name = file_path.stem
113        extension = file_path.suffix.lower()  # Extensions usually lowercase
114        
115        if case_type == 'lower':
116            new_name = original_name.lower()
117        elif case_type == 'upper':
118            new_name = original_name.upper()
119        elif case_type == 'title':
120            new_name = original_name.title()
121        else:
122            continue
123        
124        if new_name != original_name or extension != file_path.suffix:
125            renames.append((file_path, f"{new_name}{extension}"))
126    
127    return renames
128
129
130def rename_clean_filename(files):
131    """
132    Clean filenames: remove special chars, replace spaces with underscores.
133    """
134    renames = []
135    
136    for file_path in files:
137        original_name = file_path.stem
138        extension = file_path.suffix.lower()
139        
140        # Replace spaces with underscores
141        new_name = original_name.replace(' ', '_')
142        
143        # Remove special characters (keep letters, numbers, underscores, hyphens)
144        new_name = re.sub(r'[^\w\-]', '', new_name)
145        
146        # Remove multiple consecutive underscores
147        new_name = re.sub(r'_+', '_', new_name)
148        
149        # Remove leading/trailing underscores
150        new_name = new_name.strip('_')
151        
152        if new_name != original_name or extension != file_path.suffix:
153            renames.append((file_path, f"{new_name}{extension}"))
154    
155    return renames
156
157
158def check_conflicts(renames, folder_path):
159    """Check for naming conflicts."""
160    folder = Path(folder_path)
161    conflicts = []
162    new_names = {}
163    
164    for original_path, new_name in renames:
165        new_path = folder / new_name
166        if new_path.exists() and new_path != original_path:
167            conflicts.append(f"'{new_name}' already exists in folder")
168        
169        if new_name in new_names:
170            conflicts.append(f"Duplicate new name: '{new_name}'")
171        new_names[new_name] = original_path
172    
173    return conflicts
174
175
176def execute_renames(renames, dry_run=True):
177    """Execute the rename operations."""
178    if not renames:
179        print("\n⚠️  No files to rename")
180        return 0
181    
182    if dry_run:
183        print("\n📋 PREVIEW MODE (no changes will be made)")
184        print("=" * 60)
185        
186        for original_path, new_name in renames:
187            print(f"  {original_path.name}")
188            print(f"    → {new_name}")
189            print()
190        
191        print(f"Total: {len(renames)} file(s) would be renamed")
192        print("\n💡 Run with dry_run=False to execute these changes")
193        return 0
194    
195    print("\n🔄 RENAMING FILES")
196    print("=" * 60)
197    
198    renamed_count = 0
199    for original_path, new_name in renames:
200        new_path = original_path.parent / new_name
201        
202        try:
203            original_path.rename(new_path)
204            print(f"  ✓ {original_path.name}{new_name}")
205            renamed_count += 1
206        except Exception as e:
207            print(f"  ✗ {original_path.name}: {e}")
208    
209    print(f"\n✅ Renamed {renamed_count} of {len(renames)} files")
210    return renamed_count
211
212
213def main():
214    """Main entry point with example usage."""
215    
216    # ========================================
217    # CONFIGURE YOUR RENAMING OPERATION HERE
218    # ========================================
219    
220    # Folder containing files to rename
221    folder_path = "/path/to/your/files"
222    
223    # Filter by extension (optional) - set to None for all files
224    extension_filter = [".jpg", ".jpeg", ".png"]
225    
226    # ========================================
227    # CHOOSE YOUR RENAMING MODE
228    # Uncomment ONE of the sections below
229    # ========================================
230    
231    # Get files
232    try:
233        files = get_files_in_folder(folder_path, extension_filter)
234        print(f"Found {len(files)} files in {folder_path}")
235    except FileNotFoundError as e:
236        print(f"Error: {e}")
237        return
238    
239    if not files:
240        print("No matching files found!")
241        return
242    
243    # --- MODE 1: Replace pattern in filenames ---
244    # renames = rename_replace_pattern(
245    #     files,
246    #     find_text="IMG_",
247    #     replace_text="vacation_",
248    #     case_sensitive=False
249    # )
250    
251    # --- MODE 2: Sequential numbering ---
252    renames = rename_sequential(
253        files,
254        pattern="company_retreat_2025_{n}",
255        start_number=1,
256        padding=3
257    )
258    
259    # --- MODE 3: Add prefix ---
260    # renames = rename_add_prefix(files, prefix="2025_")
261    
262    # --- MODE 4: Add suffix ---
263    # renames = rename_add_suffix(files, suffix="_final")
264    
265    # --- MODE 5: Change case ---
266    # renames = rename_change_case(files, case_type='lower')
267    
268    # --- MODE 6: Clean filenames ---
269    # renames = rename_clean_filename(files)
270    
271    # ========================================
272    # CHECK FOR CONFLICTS
273    # ========================================
274    
275    conflicts = check_conflicts(renames, folder_path)
276    if conflicts:
277        print("\n❌ CONFLICTS DETECTED:")
278        for conflict in conflicts:
279            print(f"  • {conflict}")
280        print("\nResolve conflicts before proceeding.")
281        return
282    
283    # ========================================
284    # EXECUTE (set dry_run=False to apply changes)
285    # ========================================
286    
287    execute_renames(renames, dry_run=True)
288
289
290if __name__ == "__main__":
291    main()

How to Run This Script

  1. Save the script as batch_renamer.py

  2. Edit the configuration in the main() function:

    • Set folder_path to your folder
    • Choose your extension filter
    • Uncomment the renaming mode you want
  3. Run in preview mode first:

    bash
    1python batch_renamer.py
  4. Review the preview output:

    Prompt
    Found 50 files in /Users/yourname/Photos
    
    📋 PREVIEW MODE (no changes will be made)
    ============================================================
      IMG_0001.jpg
        → company_retreat_2025_001.jpg
    
      IMG_0002.jpg
        → company_retreat_2025_002.jpg
    
    Total: 50 file(s) would be renamed
    
    💡 Run with dry_run=False to execute these changes
  5. Execute the renames by changing dry_run=True to dry_run=False

Customization Options

Rename Using File Date

Add creation date to filenames:

python
1from datetime import datetime
2
3def rename_with_date(files, pattern):
4    """Add file modification date to filename."""
5    renames = []
6    
7    for file_path in files:
8        mod_time = file_path.stat().st_mtime
9        date_str = datetime.fromtimestamp(mod_time).strftime("%Y%m%d")
10        
11        new_name = pattern.replace("{date}", date_str)
12        new_name = f"{new_name}{file_path.suffix}"
13        
14        renames.append((file_path, new_name))
15    
16    return renames
17
18# Usage: rename_with_date(files, "photo_{date}")
19# Result: photo_20251104.jpg

Remove Numbers from Filenames

python
1def rename_remove_numbers(files):
2    """Remove all numbers from filenames."""
3    renames = []
4    
5    for file_path in files:
6        new_name = re.sub(r'\d+', '', file_path.stem)
7        new_name = new_name.strip('_- ')  # Clean up leftover separators
8        
9        if new_name:  # Don't create empty names
10            renames.append((file_path, f"{new_name}{file_path.suffix}"))
11    
12    return renames

Regex Pattern Matching

For complex patterns:

python
1def rename_regex(files, regex_pattern, replacement):
2    """Use regex for advanced pattern matching."""
3    renames = []
4    pattern = re.compile(regex_pattern)
5    
6    for file_path in files:
7        new_name = pattern.sub(replacement, file_path.stem)
8        
9        if new_name != file_path.stem:
10            renames.append((file_path, f"{new_name}{file_path.suffix}"))
11    
12    return renames
13
14# Example: Remove timestamps like "_20251104_143022"
15# rename_regex(files, r'_\d{8}_\d{6}', '')

Common Issues & Solutions

IssueSolution
"File not found" errorDouble-check folder_path is correct and exists
No files foundCheck extension_filter matches your file types
Permission deniedClose files that are open in other programs
Duplicate conflictThe new name already exists; adjust your pattern
Files out of orderFiles are sorted alphabetically; use padding for numbers

Taking It Further

Create an Undo File

Save original names for reverting:

python
1import json
2
3def save_undo_file(renames, undo_path="rename_undo.json"):
4    """Save rename mapping for undo capability."""
5    undo_data = [
6        {"original": str(orig), "new": new_name}
7        for orig, new_name in renames
8    ]
9    
10    with open(undo_path, 'w') as f:
11        json.dump(undo_data, f, indent=2)
12    
13    print(f"Undo file saved: {undo_path}")

Add Command-Line Arguments

Make the script more flexible:

python
1import argparse
2
3parser = argparse.ArgumentParser(description='Batch rename files')
4parser.add_argument('folder', help='Folder containing files')
5parser.add_argument('--find', help='Text to find')
6parser.add_argument('--replace', help='Text to replace with')
7parser.add_argument('--prefix', help='Prefix to add')
8parser.add_argument('--execute', action='store_true', help='Execute renames')
9
10args = parser.parse_args()

Conclusion

You've built a powerful, safe batch file renamer. The dry-run mode means you'll never accidentally destroy your files—you can preview every change before it happens.

The modular design lets you mix and match operations. Need to clean filenames AND add a prefix? Chain the functions together. Want to add new renaming modes? Just add another function following the same pattern.

File renaming is one of those tasks that seems simple until you have 500 files to process. Now you have a tool that handles any scenario in seconds.

Your filenames, your rules.

Sponsored Content

Interested in advertising? Reach automation professionals through our platform.

Share this article