Rename files with Python: preview a batch first

Written by

in

Renaming one file is easy. Renaming a folder full of files is where a
small mistake gets expensive. Start by printing the old and new names.
Check the list, then try the changes on a copy of the folder.

Python’s Path.rename() changes a file’s name or
location. For a batch, build the list of changes before applying them.
The example below removes an old_ prefix from two fictional
files, then demonstrates what happens when a target name already
exists.

Run the example
without touching your files

Save the following as rename_walkthrough.py and run
python3 rename_walkthrough.py (on Windows, use
py rename_walkthrough.py if that is your Python command).
It uses the standard library and creates its own temporary folder. It
does not accept a folder path from you. The temporary files disappear
when the example finishes.

"""Preview and apply a prefix-removal rename in a disposable directory."""

from pathlib import Path
import tempfile


def plan_renames(directory, prefix="old_"):
    directory = Path(directory)
    return [(path, path.with_name(path.name[len(prefix):]))
            for path in sorted(directory.iterdir())
            if path.is_file() and path.name.startswith(prefix)]


def apply_renames(plan):
    """Rename after a collision precheck; this precheck is not atomic."""
    collisions = [target for source, target in plan if target.exists() and target != source]
    if collisions:
        raise FileExistsError(f"refusing existing target: {collisions[0].name}")
    # ponytail: sequential rename is enough for this isolated demo; use an OS-level
    # transactional strategy if concurrent writers or production paths are needed.
    for source, target in plan:
        source.rename(target)


def demo():
    with tempfile.TemporaryDirectory(prefix="rename-walkthrough-") as name:
        directory = Path(name)
        files = {"old_alpha.txt": "alpha\n", "old_beta.txt": "beta\n"}
        for filename, contents in files.items():
            (directory / filename).write_text(contents, encoding="utf-8")
        plan = plan_renames(directory)
        print("Preview:")
        for source, target in plan:
            print(f"{source.name} -> {target.name}")
        apply_renames(plan)
        print("After rename:")
        for path in sorted(directory.iterdir()):
            print(f"{path.name}: {path.read_text(encoding='utf-8').rstrip()}")
        collision_source = directory / "old_gamma.txt"
        collision_target = directory / "gamma.txt"
        collision_source.write_text("new source\n", encoding="utf-8")
        collision_target.write_text("existing target\n", encoding="utf-8")
        try:
            apply_renames(plan_renames(directory))
        except FileExistsError as error:
            print(f"Collision refused: {error}")


if __name__ == "__main__":
    demo()

Expected output:

Preview:
old_alpha.txt -> alpha.txt
old_beta.txt -> beta.txt
After rename:
alpha.txt: alpha
beta.txt: beta
Collision refused: refusing existing target: gamma.txt

What the preview does

plan_renames() collects old and new paths. It changes
nothing on disk. old_alpha.txt becomes
alpha.txt; the .txt extension stays intact
because only the leading prefix is removed. Sorting the files makes the
preview consistent. This example handles one folder, not nested
directories.

apply_renames() first checks every planned target. If a
target already exists, it raises an error before starting the batch. In
the final example, both old_gamma.txt and
gamma.txt stay in place.

Why check for existing
filenames?

Renaming has an important platform difference: an existing
destination file can be replaced on Unix, while Windows raises
FileExistsError. Python documents that behavior for os.rename().
Path.rename()
uses the same underlying behavior.

The precheck is useful in this isolated demonstration. It is not a
guarantee against another program creating a target between the check
and the rename. The batch is also not a transaction: an unexpected error
during a later rename does not undo earlier ones. Do not adapt this
directly to a shared or actively changing folder and assume it is
safe.

Before adapting it to
your own folder

Make a separate working copy and keep the originals. Inspect the full
old-to-new list before applying it. Decide what should happen to
subfolders, symbolic links and filenames that already have the desired
prefix removed. This example does not implement those policies for
arbitrary folders.

For a one-off task, your file manager’s rename tools may already be
enough. A script becomes useful when you need a repeatable naming rule
and want to test that rule before applying it.