import os import csv from datetime import datetime def search_in_file(file_path: str, needle: str, case_sensitive: bool = True): """Search for a string in a file. Returns a list of (line_number, line_text) for each match.""" matches = [] try: with open(file_path, "r", encoding="utf-8", errors="ignore") as f: text = f.read() except (OSError, UnicodeDecodeError): # Skip files that cannot be read as text return matches if not text or not needle: return matches # Prepare for case (in)sensitive search if case_sensitive: haystack = text needle_cmp = needle else: haystack = text.lower() needle_cmp = needle.lower() start = 0 while True: idx = haystack.find(needle_cmp, start) if idx == -1: break # Line number = number of '\n' before the match + 1 line_number = text.count("\n", 0, idx) + 1 # Get the full line where the match begins (for display) line_start = text.rfind("\n", 0, idx) line_end = text.find("\n", idx) if line_start == -1: line_start = 0 else: line_start += 1 if line_end == -1: line_end = len(text) line_text = text[line_start:line_end].strip() matches.append((line_number, line_text)) # Move past this match to find others start = idx + 1 return matches def scan(root: str, needle: str, case_sensitive: bool = True): """Recursively scan root folder and search in all files.""" results = [] for dirpath, dirnames, filenames in os.walk(root): for filename in filenames: full_path = os.path.join(dirpath, filename) file_matches = search_in_file(full_path, needle, case_sensitive) for line_number, line_text in file_matches: results.append({ "file_path": full_path, "folder": dirpath, "line_number": line_number, "line_text": line_text, }) return results def choose_folder(base_dir: str) -> str: """List subfolders of base_dir and let user pick one (or base_dir itself).""" print(f"Base directory: {base_dir}\n") # Find direct subdirectories entries = os.listdir(base_dir) subfolders = [d for d in entries if os.path.isdir(os.path.join(base_dir, d))] subfolders.sort() # Always include option 0 = current/base directory print("Select which folder to scan:") print(" 0) (this folder and all subfolders)") if subfolders: for idx, folder in enumerate(subfolders, start=1): print(f" {idx}) {folder}") else: print(" (no subfolders found, only option is 0)") while True: choice = input("\nEnter the number of the folder to scan: ").strip() if not choice.isdigit(): print("Please enter a valid number.") continue idx = int(choice) if idx == 0: return base_dir if 1 <= idx <= len(subfolders): return os.path.join(base_dir, subfolders[idx - 1]) print("Invalid choice, try again.") def write_results_to_csv(results, csv_path: str): """Write all results to a CSV file.""" fieldnames = ["file_path", "folder", "line_number", "line_text"] try: # newline='' is important on Windows to avoid blank lines with open(csv_path, "w", encoding="utf-8", newline="") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for row in results: writer.writerow(row) except OSError as e: print(f"Error writing CSV file: {e}") return False return True if __name__ == "__main__": # Use the directory where this script is located as the base script_dir = os.path.dirname(os.path.abspath(__file__)) # Step 1: Choose folder target_root = choose_folder(script_dir) print(f"\nScanning in: {target_root}\n") # Step 2: Ask what code/text to search pattern = input("Enter the exact code/text to search for: ") # Step 3: Case sensitivity case_input = input("Case-sensitive search? (y/n, default: y): ").strip().lower() case_sensitive = (case_input != "n") print("\nSearching... this may take a moment on large folders.\n") results = scan(target_root, pattern, case_sensitive=case_sensitive) if not results: print("No matches found.") else: print(f"Total matches found: {len(results)}") # Default CSV file name with timestamp default_name = f"search_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" user_name = input( f"Enter CSV file name to save results " f"(press Enter for default '{default_name}'): " ).strip() if not user_name: csv_name = default_name else: # Ensure it ends with .csv if not user_name.lower().endswith(".csv"): csv_name = user_name + ".csv" else: csv_name = user_name csv_path = os.path.join(script_dir, csv_name) if write_results_to_csv(results, csv_path): print(f"\nResults saved to CSV: {csv_path}") else: print("\nFailed to save results to CSV.")