Created
August 17, 2025 01:42
-
-
Save yanli0303/341a79363d593592ab689a50a2897dec to your computer and use it in GitHub Desktop.
Directory Flattening Script, it flattens a directory by moving all files from subdirectories to the root directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Directory Flattening Script | |
| This script accepts a path to a directory and flattens it by moving all files | |
| from subdirectories to the root directory. When file name conflicts occur, | |
| it keeps all files by adding numbered suffixes like (1), (2), etc. | |
| Usage: | |
| python flat_dir.py <directory_path> | |
| """ | |
| import os | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| def get_unique_filename(target_dir: Path, filename: str, used_names: set[str]) -> str: | |
| """ | |
| Generate a unique filename by adding numbered suffixes if conflicts exist. | |
| Args: | |
| target_dir: The target directory path | |
| filename: The original filename | |
| used_names: set of already used filenames to avoid conflicts | |
| Returns: | |
| A unique filename | |
| """ | |
| if filename not in used_names and not (target_dir / filename).exists(): | |
| used_names.add(filename) | |
| return filename | |
| # Split filename into name and extension | |
| name_part, ext_part = os.path.splitext(filename) | |
| counter = 1 | |
| while True: | |
| new_filename = f"{name_part}({counter}){ext_part}" | |
| if new_filename not in used_names and not (target_dir / new_filename).exists(): | |
| used_names.add(new_filename) | |
| return new_filename | |
| counter += 1 | |
| def flat_dir(directory_path: str) -> None: | |
| """ | |
| Flatten a directory by moving all files from subdirectories to the root. | |
| Args: | |
| directory_path: Path to the directory to flatten | |
| """ | |
| root_path = Path(directory_path).resolve() | |
| if not root_path.exists(): | |
| print(f"Error: Directory '{directory_path}' does not exist.") | |
| return | |
| if not root_path.is_dir(): | |
| print(f"Error: '{directory_path}' is not a directory.") | |
| return | |
| print(f"Flattening directory: {root_path}") | |
| # Keep track of used filenames to avoid conflicts | |
| used_names: set[str] = set() | |
| # First, collect all existing files in the root directory | |
| for item in root_path.iterdir(): | |
| if item.is_file(): | |
| used_names.add(item.name) | |
| # Collect all files from subdirectories | |
| files_to_move = [] | |
| for root, dirs, files in os.walk(root_path): | |
| current_path = Path(root) | |
| # Skip the root directory itself | |
| if current_path == root_path: | |
| continue | |
| for file in files: | |
| file_path = current_path / file | |
| files_to_move.append(file_path) | |
| if not files_to_move: | |
| print("No files found in subdirectories to move.") | |
| return | |
| print(f"Found {len(files_to_move)} files to move from subdirectories.") | |
| # Move files to root directory | |
| moved_count = 0 | |
| error_count = 0 | |
| for file_path in files_to_move: | |
| try: | |
| # Generate unique filename | |
| unique_filename = get_unique_filename(root_path, file_path.name, used_names) | |
| target_path = root_path / unique_filename | |
| # Move the file | |
| shutil.move(str(file_path), str(target_path)) | |
| if unique_filename != file_path.name: | |
| print(f"Moved: {file_path.relative_to(root_path)} -> {unique_filename}") | |
| else: | |
| print(f"Moved: {file_path.relative_to(root_path)}") | |
| moved_count += 1 | |
| except Exception as e: | |
| print(f"Error moving {file_path.relative_to(root_path)}: {e}") | |
| error_count += 1 | |
| # Remove empty directories | |
| empty_dirs_removed = 0 | |
| for root, dirs, files in os.walk(root_path, topdown=False): | |
| current_path = Path(root) | |
| # Skip the root directory itself | |
| if current_path == root_path: | |
| continue | |
| try: | |
| if not any(current_path.iterdir()): # Directory is empty | |
| current_path.rmdir() | |
| print(f"Removed empty directory: {current_path.relative_to(root_path)}") | |
| empty_dirs_removed += 1 | |
| except OSError as e: | |
| # Directory might not be empty or other permission issues | |
| print(f"Could not remove directory {current_path.relative_to(root_path)}: {e}") | |
| print("\n" + "=" * 50) | |
| print("Flattening complete!") | |
| print(f"Files moved: {moved_count}") | |
| print(f"Errors: {error_count}") | |
| print(f"Empty directories removed: {empty_dirs_removed}") | |
| def main(): | |
| """Main function to handle command line arguments and run the flattening process.""" | |
| if len(sys.argv) != 2: | |
| print("Usage: python flat_dir.py <directory_path>") | |
| print("\nExample:") | |
| print(" python flat_dir.py /path/to/directory") | |
| return | |
| directory_path = sys.argv[1] | |
| # Confirm with user before proceeding | |
| print(f"This will flatten the directory: {directory_path}") | |
| print("All files from subdirectories will be moved to the root directory.") | |
| print("Empty subdirectories will be removed.") | |
| print("This operation cannot be easily undone.") | |
| response = input("\nDo you want to continue? (y/N): ").strip().lower() | |
| if response not in ["y", "yes"]: | |
| print("Operation cancelled.") | |
| return | |
| flat_dir(directory_path) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.