move-no-collisions-recurse.py hinzugefügt

This commit is contained in:
Sebastian Mondial 2025-02-21 10:40:59 +00:00
parent bca74a9a4b
commit 0f867a5f8b

View file

@ -0,0 +1,112 @@
#!/usr/bin/env python3
import os
import sys
import shutil
from pathlib import Path
import argparse
from datetime import datetime
def get_new_filename(dest_path: Path, filename: str) -> Path:
"""Generate a new filename if a collision occurs by adding -01, -02, etc."""
name = Path(filename).stem
suffix = Path(filename).suffix
counter = 1
new_path = dest_path / filename
while new_path.exists():
new_filename = f"{name}-{counter:02d}{suffix}"
new_path = dest_path / new_filename
counter += 1
return new_path
def move_files(source_dir: str, dest_dir: str, extensions: list, dry_run: bool = False) -> tuple:
"""
Move files with specific extensions from source_dir to dest_dir.
Returns tuple of (files_processed, total_size)
"""
source_path = Path(source_dir).resolve()
dest_path = Path(dest_dir).resolve()
files_processed = 0
total_size = 0
if not source_path.exists():
print(f"Error: Source directory '{source_dir}' does not exist!")
sys.exit(1)
# Create destination if it doesn't exist
if not dry_run:
dest_path.mkdir(parents=True, exist_ok=True)
# Convert extensions to lowercase for case-insensitive matching
extensions = [ext.lower() for ext in extensions]
# Walk through source directory and all subdirectories
for root, _, files in os.walk(source_path):
for file in files:
file_path = Path(root) / file
if file_path.suffix.lower() in extensions:
files_processed += 1
total_size += file_path.stat().st_size
if dry_run:
print(f"Would move: {file_path} -> {dest_path / file}")
continue
# Get new filename if collision occurs
new_path = get_new_filename(dest_path, file)
# Move file and preserve timestamps
shutil.copy2(file_path, new_path)
os.remove(file_path)
print(f"Moved: {file_path} -> {new_path}")
return files_processed, total_size
def main():
parser = argparse.ArgumentParser(
description="Move files with specific extensions from source directory to destination directory"
)
parser.add_argument("source", nargs="?", help="Source directory")
parser.add_argument("destination", nargs="?", help="Destination directory")
parser.add_argument(
"-ext",
"--extensions",
nargs="+",
help="File extensions to move (e.g., .jpg .png .gif)"
)
parser.add_argument(
"-dry",
"--dry-run",
action="store_true",
help="Perform a dry run without moving files"
)
args = parser.parse_args()
if not all([args.source, args.destination, args.extensions]):
parser.print_help()
print("\nExample usage:")
print(" move_files.py /source/dir /dest/dir -ext .jpg .png .gif")
print(" move_files.py /source/dir /dest/dir -ext .jpg -dry")
sys.exit(1)
# Ensure extensions start with dot
extensions = [ext if ext.startswith('.') else f'.{ext}' for ext in args.extensions]
files_processed, total_size = move_files(
args.source,
args.destination,
extensions,
args.dry_run
)
# Print summary
action = "Would move" if args.dry_run else "Moved"
print(f"\nSummary:")
print(f"Files {action}: {files_processed}")
print(f"Total size: {total_size / 1024 / 1024:.2f} MB")
if __name__ == "__main__":
main()