Created
September 5, 2025 13:47
-
-
Save brayevalerien/4d6c195d743c0ff999c866342cf1d89b to your computer and use it in GitHub Desktop.
Search and replace text in all text files within a directory (recursively). Usage: python search_replace.py <directory> <search_text> <replace_text>
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 | |
| """ | |
| search_replace.py - Search and replace text in all text files within a directory (recursively) | |
| Usage: | |
| python search_replace.py <directory> <search_text> <replace_text> | |
| Example: | |
| python search_replace.py "C:\Documents\Project" "TODO" "DONE" | |
| python search_replace.py . "old_function" "new_function" | |
| Features: | |
| - Recursively processes all subdirectories | |
| - UTF-8 encoding support | |
| - Error handling for inaccessible files | |
| - Reports modified files and total count | |
| - Preserves file permissions and timestamps | |
| Requirements: | |
| - Python 3.6+ | |
| - Windows/Linux/MacOS compatible | |
| Author: Valérien (brayevalerien) | |
| License: MIT | |
| """ | |
| import os | |
| import sys | |
| from pathlib import Path | |
| TEXT_EXTENSIONS = {'.txt', '.md', '.log', '.csv', '.json', '.xml', '.yaml', '.yml', | |
| '.ini', '.cfg', '.conf', '.py', '.js', '.html', '.css', '.sql'} | |
| def search_replace(directory, search_text, replace_text, extensions=TEXT_EXTENSIONS): | |
| """ | |
| Search and replace text in all matching files within a directory. | |
| Args: | |
| directory (str): Path to target directory | |
| search_text (str): Text to search for | |
| replace_text (str): Text to replace with | |
| extensions (set): Set of file extensions to process | |
| Returns: | |
| int: Number of files modified | |
| """ | |
| directory = Path(directory) | |
| if not directory.exists(): | |
| print(f"Error: Directory '{directory}' does not exist") | |
| return 0 | |
| if not directory.is_dir(): | |
| print(f"Error: '{directory}' is not a directory") | |
| return 0 | |
| modified_count = 0 | |
| error_count = 0 | |
| # Walk through directory tree | |
| for filepath in directory.rglob('*'): | |
| if filepath.is_file() and filepath.suffix.lower() in extensions: | |
| try: | |
| with open(filepath, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| if search_text in content: | |
| # Replace and write back | |
| new_content = content.replace(search_text, replace_text) | |
| with open(filepath, 'w', encoding='utf-8') as f: | |
| f.write(new_content) | |
| modified_count += 1 | |
| print(f"Modified: {filepath}") | |
| except PermissionError: | |
| error_count += 1 | |
| print(f"Permission denied: {filepath}") | |
| except UnicodeDecodeError: | |
| error_count += 1 | |
| print(f"Encoding error: {filepath}") | |
| except Exception as e: | |
| error_count += 1 | |
| print(f"Error processing {filepath}: {e}") | |
| # Summary | |
| print(f"\n{'_'*50}") | |
| print(f"Summary:") | |
| print(f" - Files modified: {modified_count}") | |
| if error_count > 0: | |
| print(f" - Files with errors: {error_count}") | |
| print(f"{'_'*50}") | |
| return modified_count | |
| def main(): | |
| """Main entry point for command-line usage.""" | |
| if len(sys.argv) != 4: | |
| print("Usage: python search_replace.py <directory> <search_text> <replace_text>") | |
| print("\nExamples:") | |
| print(' python search_replace.py "C:\\Projects" "localhost" "127.0.0.1"') | |
| print(' python search_replace.py . "print(" "logging.debug("') | |
| sys.exit(1) | |
| directory = sys.argv[1] | |
| search_text = sys.argv[2] | |
| replace_text = sys.argv[3] | |
| print(f"Search and Replace Operation") | |
| print(f"{'_'*50}") | |
| print(f"Directory: {os.path.abspath(directory)}") | |
| print(f"Search for: '{search_text}'") | |
| print(f"Replace with: '{replace_text}'") | |
| print(f"File types: {', '.join(sorted(TEXT_EXTENSIONS))}") | |
| print(f"{'_'*50}\n") | |
| # Perform search and replace | |
| modified = search_replace(directory, search_text, replace_text) | |
| # Exit with appropriate code | |
| sys.exit(0 if modified > 0 else 1) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment