Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Automate File Renaming

Automate file renaming with os and shutil — Python tutorial, lesson 74. Hands-on steps, troubleshooting, and next steps.

Focus: automate file renaming with os and shutil

Sponsored

Manually renaming hundreds of files — adding prefixes, changing extensions, or normalizing timestamps — is tedious, error-prone, and a waste of developer time. This lesson shows you how to automate file renaming with os and shutil in Python so you can transform an entire directory with a few lines of code.

The problem this lesson solves

Without automation, renaming files means either clicking through a GUI or writing fragile shell one-liners that break on spaces or special characters. Worse, manual renaming doesn't leave a record of what changed. When you need to rename 500 .jpg files to photo_001.jpg, photo_002.jpg, etc., doing it by hand is impossible.

Common pain points this lesson eliminates: - Inconsistent naming conventions across a project - Time wasted on repetitive GUI actions - Errors from typos — accidentally overwriting files - Lack of undo / dry-run capabilities

By the end of this lesson, you'll write Python scripts that rename files in bulk safely and predictably.

Core concept / mental model

Think of file renaming as three atomic operations: 1. Locate the file on disk (its current path) 2. Construct the new name (the desired path) 3. Move the file from old path to new path

Under the hood, a rename is just a move operation within the same filesystem. Python's os.rename() and shutil.move() both do this, but shutil.move() is more flexible because it works across different drives or devices.

💡 Pro tip: A rename and a move are the same system call — os.rename() just cannot copy across filesystem boundaries. shutil.move() handles both cases.

The mental model in code

Concept Python analog
Current path os.path.join('/folder', 'old_name.txt')
New path os.path.join('/folder', 'new_name.txt')
Atomic rename os.rename(old, new)
Safe cross-device move shutil.move(old, new)

How it works step by step

Follow this universal sequence for every rename script you write:

Step 1: List target files

Use os.listdir() or glob.glob() to collect files that match your criteria.

Step 2: Build new names

Generate the new filename using Python string methods or f-strings. You'll likely need os.path.splitext() to separate the base name from the extension.

Step 3: Validate before moving

Check that the new name doesn't already exist, the file is readable, and you're not about to overwrite something important.

Step 4: Perform the rename

Call os.rename() (fast, within same drive) or shutil.move() (cross-device safe).

⚠️ Warning: os.rename() will silently overwrite the destination if it exists. Always check first.

Hands-on walkthrough

Let's build a script that renames all .txt files in a folder to include a numeric prefix: note_001.txt, note_002.txt, etc.

Setup: create a test folder

mkdir test_rename
cd test_rename
touch alpha.txt beta.txt gamma.txt
touch junk.png   # should be ignored

Basic renaming with os.rename()

import os

folder = '.'
files = [f for f in os.listdir(folder) if f.endswith('.txt')]
files.sort()  # ensures stable ordering

for idx, filename in enumerate(files, start=1):
    old_path = os.path.join(folder, filename)
    name, ext = os.path.splitext(filename)
    new_name = f"note_{idx:03d}{ext}"
    new_path = os.path.join(folder, new_name)

    if not os.path.exists(new_path):
        os.rename(old_path, new_path)
        print(f"Renamed: {filename} → {new_name}")
    else:
        print(f"Skipped: {new_name} already exists")

Expected output:

Renamed: alpha.txt → note_001.txt
Renamed: beta.txt → note_002.txt
Renamed: gamma.txt → note_003.txt

Using shutil.move() for cross-device safety

If you're renaming files that may be on a network drive or USB stick, use shutil.move().

import os
import shutil

folder = '.'
for idx, fname in enumerate(os.listdir(folder), start=1):
    if not fname.endswith('.txt'):
        continue
    old = os.path.join(folder, fname)
    new = os.path.join(folder, f"doc_{idx:03d}.txt")
    # shutil.move() handles both same-drive (rename) and cross-drive (copy+delete)
    shutil.move(old, new)

Dry-run mode for safety

import os

def dry_rename(old, new):
    print(f"DRY RUN: would rename {old} → {new}")
    # In real mode, call os.rename(old, new)

dry_rename('old.txt', 'new.txt')

🧪 Try this: Add a --dry-run flag to your real script from sys.argv. It's a great practice for production.

Compare options / when to choose what

Feature os.rename() shutil.move()
Speed Fast (single system call) Slower (copy+delete on diff drives)
Cross-device ❌ Fails with OSError ✅ Works
Overwrites silently ✅ (dangerous) ✅ (also dangerous)
Metadata preserved Yes Copies permissions, but timestamps may change
Python version All versions 2.5+

Rule of thumb: - Use os.rename() when all files are on the same local drive and you need speed. - Use shutil.move() when files might be on different drives or you want a single function for both rename and move. - Always wrap in a check if not os.path.exists(new_path) to prevent accidental overwrites.

Variations worth knowing

  • pathlib approach: Path.rename() is cleaner for modern Python but doesn't handle cross-device.
  • Regex renaming: Use re.sub() for pattern-based changes like date normalization.
  • Watchdog integration: Combine with watchdog to auto-rename files on creation.

Troubleshooting & edge cases

FileNotFoundError

Problem: You try to rename a file that doesn't exist. Fix: Always check os.path.exists(old_path) before renaming.

PermissionError

Problem: The script lacks write permission on the directory. Fix: Run with appropriate permissions or check os.access(folder, os.W_OK).

Collision (silent overwrite)

Problem: Two files map to the same new name — one vanishes. Fix: Always check os.path.exists(new_path) before calling rename.

Files with unicode or spaces

Problem: Scripts that use str.replace() or split() break. Fix: Use os.path functions and raw strings (r"path") for paths. Avoid assuming filenames are simple ASCII.

Error Likely cause Solution
OSError: [Errno 18] Invalid cross-device link os.rename() across drives Use shutil.move()
FileNotFoundError Old path is wrong Print old_path before rename
Duplicate file names Unintended collision Add uniqueness with UUID or timestamp

Edge case: renaming with case changes on case-insensitive systems

On Windows or macOS, renaming Photo.JPG to photo.jpg might fail because the filesystem sees same filename. Solution: rename to a temp name first, then to final name.

temp = os.path.join(folder, '__tmp_rename')
os.rename(old_path, temp)
os.rename(temp, new_path)

What you learned & what's next

You now know how to automate file renaming with os and shutil in Python. You can: - List and filter files using os.listdir() and os.path - Build new names with f-strings and os.path.splitext() - Execute safe renames with collision checks - Choose between os.rename() and shutil.move() for your use case

Next lesson in the track: Organizing files into folders by date or type — a natural extension of bulk operations where shutil.move() becomes essential.

Practice recap

Create a script that renames all .png files in your Downloads folder to include a timestamp prefix (like img_20250305_1423.png). Add a --dry-run flag so you can review the changes first. Then run the real rename. To level up, add a --revert option that undoes the rename using a saved log file.

Common mistakes

  • Forgetting to call os.path.exists(new_path) before renaming, then accidentally overwriting a file that already has the target name.
  • Using os.rename() across filesystem boundaries (e.g., from /home to a USB mount) — this raises OSError: [Errno 18] Invalid cross-device link. Always use shutil.move() in that case.
  • Assuming filenames are case-sensitive on Windows/macOS — renaming abc.TXT to abc.txt may silently fail because the filesystem treats them as identical.
  • Building the new path with simple string concatenation instead of os.path.join(), causing errors on paths with trailing slashes or missing separators.

Variations

  1. Use pathlib.Path.rename() for an object-oriented, cleaner syntax (available in Python 3.4+).
  2. Combine with re.sub() for pattern-based renaming, e.g. normalizing dates from 2023-12-25_report.txt to report_20231225.txt.
  3. Wrap the script in a with contextlib.suppress(OSError): to silently ignore permission errors during dry runs.

Real-world use cases

  • A photographer renames 1000+ raw camera files IMG_1234.CR2 into a date-based folder structure like 2024/01/15/photo_001.CR2 using shutil.move().
  • A data scientist anonymizes CSV exports by replacing original participant IDs with sequential codes (user_001.csv, user_002.csv) — always with a dry-run first.
  • A DevOps engineer renames deployment artifacts in a CI pipeline using os.rename() to append build numbers to JAR files before uploading to Nexus.

Key takeaways

  • File renaming in Python is just moving a file from an old path to a new path using os.rename() or shutil.move().
  • Always check os.path.exists(new_path) before renaming to avoid accidental overwrites.
  • Use shutil.move() for cross-device operations and os.rename() for same-device speed.
  • Build new paths with os.path.join() and split extensions with os.path.splitext() to avoid bugs with spaces and unicode.
  • Implement a dry-run mode (--dry-run) in production scripts to review changes before executing.
  • For case-change renames on case-insensitive filesystems, rename to a temporary name first.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.