================================================================================ PRACTICAL PYTHON SCRIPTING - CHALLENGE 2 SOLUTION Chapter 1: Paths & File System Navigation Challenge: File Discovery and Organization ================================================================================ PROBLEM: Create a script that: 1. Finds all .txt files in a "source" subdirectory 2. Creates a "backup" subdirectory 3. Copies each .txt file to the backup directory 4. Reports how many files were copied and where they went SOLUTION: ================================================================================ from pathlib import Path import shutil # Find the script's directory script_dir = Path(__file__).parent # Create paths to source and backup directories source_dir = script_dir / "source" backup_dir = script_dir / "backup" print(f"Script location: {script_dir}") print(f"Source directory: {source_dir}") print(f"Backup directory: {backup_dir}\n") # Check if source directory exists if not source_dir.exists(): print(f"āŒ Source directory not found: {source_dir}") exit(1) # Create backup directory if needed if not backup_dir.exists(): print(f"šŸ“ Creating backup directory...") backup_dir.mkdir() # Find all .txt files in source directory txt_files = list(source_dir.glob("*.txt")) if not txt_files: print(f"āš ļø No .txt files found in {source_dir}") else: print(f"šŸ“„ Found {len(txt_files)} .txt file(s)\n") # Copy each file to backup directory copied_count = 0 for source_file in txt_files: backup_file = backup_dir / source_file.name try: shutil.copy2(source_file, backup_file) print(f"āœ… Copied: {source_file.name}") copied_count += 1 except Exception as e: print(f"āŒ Failed to copy {source_file.name}: {e}") print(f"\nāœ… Successfully copied {copied_count} file(s) to {backup_dir}") ================================================================================ HOW IT WORKS: ================================================================================ 1. SETUP from pathlib import Path import shutil - Path: Work with file paths - shutil: Copy files (short for "shell utilities") 2. DIRECTORY PATHS script_dir = Path(__file__).parent source_dir = script_dir / "source" backup_dir = script_dir / "backup" - Find the script location - Create paths relative to the script - Use / to append directory names 3. VALIDATION if not source_dir.exists(): print(f"āŒ Source directory not found") exit(1) - Check if source directory exists - Exit if not (can't copy from a directory that doesn't exist) - exit(1) means "exit with error code 1" 4. CREATE BACKUP DIRECTORY if not backup_dir.exists(): backup_dir.mkdir() - Check if backup directory exists - Create it if needed (using .mkdir()) 5. FIND FILES txt_files = list(source_dir.glob("*.txt")) - .glob("*.txt") finds all .txt files - list() converts the results to a list - The * means "any characters" - So "*.txt" means "anything.txt" 6. COPY FILES for source_file in txt_files: backup_file = backup_dir / source_file.name shutil.copy2(source_file, backup_file) - Loop through each source file - Create the destination path using / - source_file.name gets just the filename (not the directory) - shutil.copy2() copies the file and preserves metadata - try/except handles errors gracefully 7. REPORT Print results showing what was copied and where ================================================================================ TESTING THE SOLUTION: ================================================================================ To test this script: 1. Create the directory structure: mkdir source mkdir source/sample1.txt mkdir source/sample2.txt mkdir source/sample3.txt Or use Python: >>> from pathlib import Path >>> Path("source").mkdir(exist_ok=True) >>> (Path("source") / "file1.txt").write_text("Hello") >>> (Path("source") / "file2.txt").write_text("World") 2. Create a file called "file_backup.py" with the solution 3. Run it: python file_backup.py Output: Script location: /home/philip/scripts Source directory: /home/philip/scripts/source Backup directory: /home/philip/scripts/backup šŸ“„ Found 3 .txt file(s) āœ… Copied: file1.txt āœ… Copied: file2.txt āœ… Copied: file3.txt āœ… Successfully copied 3 file(s) to /home/philip/scripts/backup 4. Check the backup directory: ls backup/ # Shows: file1.txt, file2.txt, file3.txt ================================================================================ WHAT EACH PART DOES: ================================================================================ shutil.copy2(source, destination) - Copies a file from source to destination - .copy2() preserves file metadata (timestamps, permissions) - .copy() would just copy the content without metadata *.txt - Pattern that matches: file1.txt, test.txt, anything.txt - Pattern does NOT match: file.text, file.doc, file (no extension) source_file.name - Gets just the filename: "file1.txt" - Not the full path: "/home/philip/scripts/source/file1.txt" - Useful when building new paths glob("*.txt") - Finds files matching a pattern in a directory - "*.txt" means "any filename with .txt extension" - "test-*.txt" would match: test-1.txt, test-report.txt, etc. - "*.py" would match: script.py, main.py, etc. try/except - try: attempt to copy the file - except: if an error occurs, catch it and print a message - Prevents the script from crashing if one file fails exit(1) - Stop the script immediately - The number (1) is the exit code - 0 = success, anything else = error - Useful for telling the OS that something went wrong ================================================================================ ALTERNATIVE APPROACHES: ================================================================================ Approach 1: Using pathlib only (without shutil) from pathlib import Path source_dir = Path(__file__).parent / "source" backup_dir = Path(__file__).parent / "backup" backup_dir.mkdir(exist_ok=True) for source_file in source_dir.glob("*.txt"): backup_file = backup_dir / source_file.name backup_file.write_bytes(source_file.read_bytes()) print(f"Copied: {source_file.name}") # More explicit but slower for large files Approach 2: Recursive search (nested directories) from pathlib import Path import shutil source_dir = Path(__file__).parent / "source" backup_dir = Path(__file__).parent / "backup" backup_dir.mkdir(exist_ok=True) # .rglob() searches recursively (all subdirectories too) for source_file in source_dir.rglob("*.txt"): relative_path = source_file.relative_to(source_dir) backup_file = backup_dir / relative_path # Create subdirectories if needed backup_file.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source_file, backup_file) Approach 3: With progress bar from pathlib import Path import shutil source_dir = Path(__file__).parent / "source" backup_dir = Path(__file__).parent / "backup" backup_dir.mkdir(exist_ok=True) txt_files = list(source_dir.glob("*.txt")) total = len(txt_files) for index, source_file in enumerate(txt_files, 1): backup_file = backup_dir / source_file.name shutil.copy2(source_file, backup_file) progress = (index / total) * 100 print(f"[{progress:3.0f}%] Copied: {source_file.name}") ================================================================================ ERROR HANDLING DETAILS: ================================================================================ The try/except block catches different types of errors: try: shutil.copy2(source_file, backup_file) except FileNotFoundError: # Source file doesn't exist (shouldn't happen in this case) print(f"File not found: {source_file}") except PermissionError: # Don't have permission to read source or write to backup print(f"Permission denied: {source_file}") except Exception as e: # Catch any other error print(f"Unexpected error: {e}") The solution uses "except Exception" which catches ALL errors. For production code, you'd catch specific errors. But for learning, this is fine. ================================================================================ REAL-WORLD APPLICATIONS: ================================================================================ This pattern is used in many real scripts: 1. BACKUP SYSTEMS Find all files in a source directory, copy them to backup storage 2. LOG ROTATION Find old log files, move them to an archive directory 3. FILE PROCESSING PIPELINES Find input files, process them, save results to output directory 4. MEDIA MANAGEMENT Find photos by type (*.jpg, *.png), organize into folders 5. DEPLOYMENT SCRIPTS Find configuration files, copy to production directory ================================================================================ KEY TAKEAWAYS: ================================================================================ āœ… Use .glob() to find files matching a pattern āœ… Use .name to get just the filename from a full path āœ… Use shutil.copy2() to copy files (preserves metadata) āœ… Use try/except to handle errors gracefully āœ… Use / to build new paths from existing ones āœ… Use .mkdir(exist_ok=True) to create directories safely āœ… Always check if directories exist before using them āœ… Use relative paths from __file__ for portability You now have the tools to write real utility scripts that find, organize, and process files! ================================================================================