move-no-collisions-recurse.py hinzugefügt
This commit is contained in:
parent
bca74a9a4b
commit
0f867a5f8b
1 changed files with 112 additions and 0 deletions
112
move-no-collisions-recurse.py
Normal file
112
move-no-collisions-recurse.py
Normal 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()
|
Loading…
Add table
Reference in a new issue