메뉴 여닫기
개인 메뉴 토글
로그인하지 않음
만약 지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.

파이썬 폴더 내부 텍스트 검색

데브카페
Devcafe (토론 | 기여)님의 2025년 9월 26일 (금) 15:58 판 (새 문서: <source lang=python> import os import re from pathlib import Path import argparse import sys from datetime import datetime class TextFileSearcher: def __init__(self): # 기본 텍스트 파일 확장자 self.text_extensions = {'.txt', '.log', '.sql', '.py', '.java', '.js', '.html', '.xml', '.json', '.csv', '.ini', '.cfg', '.conf', '.properties', '.yml', '.yaml', '.md', '.bat', '.sh'}...)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)

import os
import re
from pathlib import Path
import argparse
import sys
from datetime import datetime

class TextFileSearcher:
    def __init__(self):
        # 기본 텍스트 파일 확장자
        self.text_extensions = {'.txt', '.log', '.sql', '.py', '.java', '.js', '.html', 
                              '.xml', '.json', '.csv', '.ini', '.cfg', '.conf', 
                              '.properties', '.yml', '.yaml', '.md', '.bat', '.sh'}
        
    def search_files(self, root_path, search_text, case_sensitive=False, 
                    use_regex=False, file_extensions=None, encoding='utf-8'):
        """
        지정된 폴더와 하위 디렉토리에서 텍스트 파일 내용을 검색
        
        Args:
            root_path: 검색할 루트 폴더 경로
            search_text: 검색할 텍스트 또는 정규식
            case_sensitive: 대소문자 구분 여부
            use_regex: 정규식 사용 여부
            file_extensions: 검색할 파일 확장자 (None이면 기본 확장자 사용)
            encoding: 파일 인코딩
        """
        
        results = []
        root_path = Path(root_path)
        
        if not root_path.exists():
            print(f"경로가 존재하지 않습니다: {root_path}")
            return results
        
        # 검색할 파일 확장자 설정
        extensions = file_extensions if file_extensions else self.text_extensions
        
        # 정규식 패턴 컴파일
        if use_regex:
            try:
                pattern = re.compile(search_text, 0 if case_sensitive else re.IGNORECASE)
            except re.error as e:
                print(f"정규식 오류: {e}")
                return results
        else:
            if not case_sensitive:
                search_text = search_text.lower()
        
        print(f"검색 시작: {root_path}")
        print(f"검색어: {search_text}")
        print(f"정규식 사용: {use_regex}")
        print(f"대소문자 구분: {case_sensitive}")
        print("-" * 50)
        
        file_count = 0
        match_count = 0
        
        # 모든 파일 재귀 탐색
        for file_path in root_path.rglob('*'):
            if file_path.is_file() and file_path.suffix.lower() in extensions:
                file_count += 1
                
                try:
                    # 파일 내용 읽기 (여러 인코딩 시도)
                    content = self._read_file_with_encoding(file_path, encoding)
                    
                    if content is None:
                        continue
                    
                    # 검색 수행
                    matches = self._search_in_content(content, search_text, 
                                                    case_sensitive, use_regex, pattern if use_regex else None)
                    
                    if matches:
                        match_count += 1
                        result = {
                            'file_path': str(file_path),
                            'relative_path': str(file_path.relative_to(root_path)),
                            'matches': matches,
                            'file_size': file_path.stat().st_size,
                            'modified_time': datetime.fromtimestamp(file_path.stat().st_mtime)
                        }
                        results.append(result)
                        
                        # 실시간 결과 출력
                        print(f"✓ {result['relative_path']} ({len(matches)} 건)")
                
                except Exception as e:
                    print(f"✗ 파일 읽기 오류 {file_path}: {e}")
        
        print("-" * 50)
        print(f"검색 완료: {file_count}개 파일 중 {match_count}개 파일에서 발견")
        
        return results
    
    def _read_file_with_encoding(self, file_path, primary_encoding='utf-8'):
        """다양한 인코딩으로 파일 읽기 시도"""
        encodings = [primary_encoding, 'utf-8', 'cp949', 'euc-kr', 'latin1', 'ascii']
        
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding, errors='ignore') as f:
                    return f.read()
            except (UnicodeDecodeError, UnicodeError):
                continue
            except Exception:
                return None
        
        return None
    
    def _search_in_content(self, content, search_text, case_sensitive, use_regex, pattern):
        """파일 내용에서 텍스트 검색"""
        matches = []
        
        if use_regex:
            # 정규식 검색
            for match in pattern.finditer(content):
                line_num = content[:match.start()].count('\n') + 1
                line_content = content.split('\n')[line_num - 1].strip()
                matches.append({
                    'line_number': line_num,
                    'line_content': line_content,
                    'match_text': match.group(),
                    'start_pos': match.start(),
                    'end_pos': match.end()
                })
        else:
            # 일반 텍스트 검색
            lines = content.split('\n')
            for i, line in enumerate(lines):
                search_line = line if case_sensitive else line.lower()
                if search_text in search_line:
                    matches.append({
                        'line_number': i + 1,
                        'line_content': line.strip(),
                        'match_text': search_text
                    })
        
        return matches
    
    def print_detailed_results(self, results):
        """상세 검색 결과 출력"""
        if not results:
            print("검색 결과가 없습니다.")
            return
        
        print(f"\n=== 상세 검색 결과 ({len(results)}개 파일) ===")
        
        for i, result in enumerate(results, 1):
            print(f"\n[{i}] {result['relative_path']}")
            print(f"    크기: {result['file_size']:,} bytes")
            print(f"    수정일: {result['modified_time'].strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"    매치: {len(result['matches'])}건")
            
            for match in result['matches'][:5]:  # 최대 5개만 표시
                print(f"      라인 {match['line_number']}: {match['line_content'][:100]}")
            
            if len(result['matches']) > 5:
                print(f"      ... 외 {len(result['matches']) - 5}건 더")
    
    def save_results(self, results, output_file):
        """검색 결과를 파일로 저장"""
        try:
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(f"텍스트 파일 검색 결과\n")
                f.write(f"생성일시: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"총 {len(results)}개 파일에서 발견\n")
                f.write("=" * 80 + "\n\n")
                
                for i, result in enumerate(results, 1):
                    f.write(f"[{i}] {result['file_path']}\n")
                    f.write(f"크기: {result['file_size']:,} bytes, ")
                    f.write(f"수정일: {result['modified_time'].strftime('%Y-%m-%d %H:%M:%S')}\n")
                    f.write(f"매치 건수: {len(result['matches'])}\n")
                    
                    for match in result['matches']:
                        f.write(f"  라인 {match['line_number']}: {match['line_content']}\n")
                    
                    f.write("\n" + "-" * 80 + "\n\n")
            
            print(f"결과가 저장되었습니다: {output_file}")
            
        except Exception as e:
            print(f"파일 저장 오류: {e}")

def main():
    parser = argparse.ArgumentParser(description='폴더와 하위 디렉토리의 텍스트 파일에서 내용 검색')
    parser.add_argument('path', help='검색할 폴더 경로')
    parser.add_argument('search_text', help='검색할 텍스트')
    parser.add_argument('-i', '--ignore-case', action='store_true', help='대소문자 무시')
    parser.add_argument('-r', '--regex', action='store_true', help='정규식 사용')
    parser.add_argument('-e', '--extensions', help='파일 확장자 (쉼표로 구분, 예: .txt,.log,.sql)')
    parser.add_argument('-o', '--output', help='결과 저장할 파일명')
    parser.add_argument('--encoding', default='utf-8', help='파일 인코딩 (기본값: utf-8)')
    parser.add_argument('-d', '--detail', action='store_true', help='상세 결과 출력')
    
    args = parser.parse_args()
    
    # 파일 확장자 파싱
    extensions = None
    if args.extensions:
        extensions = set(ext.strip() for ext in args.extensions.split(','))
    
    # 검색 실행
    searcher = TextFileSearcher()
    results = searcher.search_files(
        root_path=args.path,
        search_text=args.search_text,
        case_sensitive=not args.ignore_case,
        use_regex=args.regex,
        file_extensions=extensions,
        encoding=args.encoding
    )
    
    # 결과 출력
    if args.detail:
        searcher.print_detailed_results(results)
    
    # 결과 저장
    if args.output:
        searcher.save_results(results, args.output)

# 직접 실행할 때 사용하는 간단한 인터페이스
def simple_search():
    """대화형 검색 인터페이스"""
    print("=== 텍스트 파일 검색 프로그램 ===")
    
    folder_path = input("검색할 폴더 경로: ").strip()
    search_text = input("검색할 텍스트: ").strip()
    
    use_regex = input("정규식 사용하시겠습니까? (y/n): ").strip().lower() == 'y'
    case_sensitive = input("대소문자를 구분하시겠습니까? (y/n): ").strip().lower() == 'y'
    
    searcher = TextFileSearcher()
    results = searcher.search_files(folder_path, search_text, case_sensitive, use_regex)
    
    if results:
        show_detail = input("\n상세 결과를 보시겠습니까? (y/n): ").strip().lower() == 'y'
        if show_detail:
            searcher.print_detailed_results(results)
        
        save_file = input("결과를 파일로 저장하시겠습니까? (파일명 입력 또는 Enter): ").strip()
        if save_file:
            searcher.save_results(results, save_file)

if __name__ == "__main__":
    # 명령행 인수가 있으면 CLI 모드, 없으면 대화형 모드
    if len(sys.argv) > 1:
        main()
    else:
        simple_search()

Comments