Python script to fix mv --backup=numbered file extensions

After using mv --backup=numbered, you’ll end up with a lot of files with names such as

001.jpg.~1~
001.jpg.~2~
001.jpg.~3~

etc.

The following Python script renames those files to

001.1.jpg
001.2.jpg
001.3.jpg

preserving the original file extension.

#!/usr/bin/env python3
import os
import re
import argparse

def rename_backup_files(directory, recursive=False):
    # Regular expression to match files like "161.jpg.~2~"
    pattern = re.compile(r"^(.*)\.\~(\d+)\~$")

    # Get all files in current directory
    for item in os.listdir(directory):
        item_path = os.path.join(directory, item)
        
        # If recursive is True and item is a directory, process it
        if recursive and os.path.isdir(item_path):
            rename_backup_files(item_path, recursive)
            continue

        match = pattern.match(item)
        if match:
            base_name = match.group(1)  # e.g., "161.jpg"
            number = int(match.group(2))  # e.g., 2

            # Extract base file name and extension
            name, ext = os.path.splitext(base_name)

            # Create the new filename
            new_filename = f"{name}.{number}{ext}"

            # Increment the number until the new filename is unique
            while os.path.exists(os.path.join(directory, new_filename)):
                number += 1
                new_filename = f"{name}.{number}{ext}"

            # Rename the file
            old_path = os.path.join(directory, item)
            new_path = os.path.join(directory, new_filename)
            os.rename(old_path, new_path)
            print(f"Renamed: {os.path.join(directory, item)} -> {os.path.join(directory, new_filename)}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Rename backup files in a directory.")
    parser.add_argument("directory", type=str, help="Path to the directory containing the files to rename.")
    parser.add_argument("-r", "--recursive", action="store_true", 
                      help="Recursively process subdirectories")
    args = parser.parse_args()

    rename_backup_files(args.directory, args.recursive)