Author: Morgan Reed

  • Rename files with Python: preview a batch first

    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.