[에러 해결] [Python] 파일 특정 조건 만족 라인 추출 및 다른 파일에 쓰기 해결 방법

안녕하세요, 구글 SEO와 사용자 경험(UX) 최적화에 정통한 기술 블로그 전문 에디터입니다. 오늘은 파이썬 스크립트를 사용하여 텍스트 파일에서 특정 조건을 만족하는 라인과 그 다음 라인을 추출하여 다른 파일에 쓰는 방법에 대해 알아보겠습니다. 개발자분들이 직면할 수 있는 파일 처리 로직 구현의 어려움을 해소하고, 더 효율적인 코드 작성에 도움을 드리고자 합니다.

1. 에러 발생 상황

원본 질문자께서는 특정 조건을 만족하는 라인과 그 아래 라인을 원본 파일에서 복사하여 새 파일에 작성하는 파이썬 스크립트를 만들고 싶어 했습니다. 여기서 조건은 “숫자에 두 번째 마지막 자리에 5가 포함된 라인”이었습니다. 하지만 제공된 초기 코드는 다음과 같았습니다.

file_to_read ="BB-Talk Messages.txt"
write_to_file ="BB-Gendered Dialogue Leftovers.txt"

file = open(file_to_read, "r")
data = file.read()
file.close()

with open (write_to_file, "a") as file:
    file.write(data)
print('completed')

이 코드는 단순히 원본 파일(BB-Talk Messages.txt)의 전체 내용을 읽어와 대상 파일(BB-Gendered Dialogue Leftovers.txt)에 그대로 복사하는 역할만 수행합니다. 특정 조건을 검사하거나, 조건에 맞는 라인과 그 다음 라인만 선택적으로 복사하는 로직은 전혀 포함되어 있지 않았습니다. 따라서 질문자의 의도와는 다르게, 파일의 모든 내용이 복사되는 상황이 발생했습니다.

2. 명확한 발생 원인

문제의 핵심은 “조건부 라인 추출 및 복사” 로직의 부재입니다. 초기 코드가 제대로 동작하지 않는 원인은 다음과 같습니다.

  • 전체 파일 읽기 (file.read()): file.read() 메서드는 파일 전체 내용을 하나의 문자열로 읽어옵니다. 이는 라인별로 처리하기 어렵게 만듭니다.
  • 조건 검사 로직 없음: “두 번째 마지막 자리에 5가 포함된 숫자”라는 조건을 검사하는 구문이 전혀 없습니다. 따라서 어떤 라인도 필터링되지 않습니다.
  • 다음 라인 복사 로직 없음: 조건에 맞는 라인 바로 다음 라인을 복사해야 한다는 요구사항을 처리하는 코드가 누락되어 있습니다.
  • 파일 닫기 오류 (잠재적): file = open(...) 후 file.close()를 사용했지만, 중간에 오류 발생 시 파일이 닫히지 않을 가능성이 있습니다. (with open(...)을 사용하면 자동으로 처리됨)

결론적으로, 질문자의 목표를 달성하기 위해서는 파일을 라인 단위로 읽고, 각 라인에 대해 정규 표현식(Regular Expression)을 사용하여 조건을 검사하며, 필요에 따라 다음 라인까지 추적하여 복사하는 정교한 로직이 필요합니다.

3. 해결 방법 및 코드 예시

해결책은 다음과 같은 단계를 포함합니다.

  1. 입력 파일과 출력 파일을 with open() 구문을 사용하여 안전하게 엽니다.
  2. 입력 파일을 라인 단위로 읽어 처리합니다.
  3. 정규 표현식(regex)을 사용하여 각 라인에 “두 번째 마지막 자리에 5가 포함된 숫자”가 있는지 검사합니다.
  4. 조건에 맞는 라인이 발견되면 해당 라인과 다음 라인을 저장할 리스트에 추가합니다.
  5. 모든 처리가 끝난 후, 저장된 라인들을 출력 파일에 작성합니다.

파이썬 코드 예시

아래 코드는 위에서 언급된 모든 요구사항을 만족하는 완전한 해결책입니다. 파이썬의 re 모듈을 사용하여 정규 표현식으로 복잡한 숫자 패턴을 쉽게 찾을 수 있습니다.

import re

# 파일 경로 설정
file_to_read = "BB-Talk Messages.txt"
write_to_file = "BB-Gendered Dialogue Leftovers.txt"

# 결과를 저장할 리스트 초기화
lines_to_write = []
# 다음 라인을 복사해야 하는지 여부를 나타내는 플래그
copy_next_line = False

try:
    # 입력 파일을 UTF-8 인코딩으로 읽기
    with open(file_to_read, "r", encoding="utf-8") as infile:
        lines = infile.readlines() # 모든 라인을 리스트로 읽어옴

    # 각 라인을 순회하며 조건 검사
    for i, line in enumerate(lines):
        # 이전에 조건에 맞는 라인이 있어서 다음 라인을 복사해야 하는 경우
        if copy_next_line:
            lines_to_write.append(line)
            copy_next_line = False # 다음 라인 복사 후 플래그 초기화

        # 현재 라인에서 "두 번째 마지막 자리가 5인 숫자"를 찾기 위한 정규 표현식
        # \b      - 단어 경계 (숫자를 정확히 식별)
        # \d*     - 숫자 5 앞에 올 수 있는 0개 이상의 숫자
        # 5       - 우리가 찾는 숫자 '5'
        # \d      - 숫자 5 뒤에 올 수 있는 마지막 한 자리 숫자
        # \b      - 단어 경계
        # 예: "12356", "56", "1050" 등은 매치되지만 "12345"는 매치되지 않음.
        if re.search(r'\b\d*5\d\b', line):
            lines_to_write.append(line) # 조건에 맞는 현재 라인 추가
            copy_next_line = True # 다음 라인도 복사하도록 플래그 설정

    # 결과를 출력 파일에 쓰기 ("w" 모드는 파일을 새로 생성하거나 덮어씁니다)
    with open(write_to_file, "w", encoding="utf-8") as outfile:
        for line in lines_to_write:
            outfile.write(line)
    print(f"작업이 완료되었습니다. '{len(lines_to_write)}'개의 라인이 '{write_to_file}'에 작성되었습니다.")

except FileNotFoundError:
    print(f"오류: '{file_to_read}' 파일을 찾을 수 없습니다. 경로를 확인해주세요.")
except Exception as e:
    print(f"파일 처리 중 예상치 못한 오류가 발생했습니다: {e}")

코드 설명

  • import re: 정규 표현식을 사용하기 위해 re 모듈을 임포트합니다.
  • lines_to_write: 조건에 맞는 라인들을 임시로 저장할 리스트입니다. 파일에 직접 쓰기 전에 모든 라인을 수집하는 방식은 유연성과 안정성을 높여줍니다.
  • copy_next_line: 이 불리언 플래그는 이전 라인에서 조건이 만족되어 현재 라인을 복사해야 하는지 여부를 추적합니다.
  • with open(...): 파일을 열고 닫는 과정을 안전하게 처리합니다. encoding="utf-8"을 명시하여 다양한 문자 인코딩 문제를 방지합니다.
  • infile.readlines(): 파일의 모든 라인을 읽어 리스트 형태로 반환합니다.
  • enumerate(lines): 라인 리스트를 순회하며 각 라인과 해당 인덱스를 가져옵니다.
  • re.search(r'\b\d*5\d\b', line): 이 정규 표현식은 주어진 조건(“두 번째 마지막 자리에 5가 포함된 숫자”)을 만족하는지 검사합니다.
    • \b: 단어 경계를 나타냅니다. 숫자가 다른 문자와 분리되어 있음을 보장하여, 숫자 전체를 정확히 매치하도록 돕습니다.
    • \d*: 0개 이상의 숫자가 앞에 올 수 있습니다. (예: 56에서 5 앞에 아무것도 없음, 12356에서 123)
    • 5: 리터럴 숫자 5를 찾습니다.
    • \d: 5 뒤에 마지막 한 자리 숫자가 있음을 나타냅니다. (예: 123556에서 6)
  • 예외 처리 (try-except): FileNotFoundError와 일반적인 Exception을 처리하여 스크립트의 안정성을 높였습니다.

질문자께서 “검색 조건이 read 함수에 들어가야 하는지, 아니면 그와 write 함수 사이에 새로운 함수에 들어가야 하는지” 문의하셨는데, 위 예시처럼 read 과정에서 라인별로 조건을 검사하고, 결과를 임시 리스트에 저장한 뒤, 마지막에 한 번에 write 하는 방식이 가장 일반적이고 효율적입니다. 조건을 별도 함수로 분리하여 코드의 가독성을 높일 수도 있습니다.

4. 향후 예방을 위한 팁

유사한 파일 처리 로직 구현 시 다음과 같은 팁을 참고하시면 좋습니다.

  1. 파일 처리 시 with open() 사용: 파일을 열고 닫는 과정을 자동으로 처리하여 자원 누수와 잠재적 오류를 방지합니다.
  2. 라인 단위 처리 습관화: 대용량 파일을 처리할 때는 file.read() 대신 for line in file: 또는 file.readlines()를 사용하여 메모리 효율성을 높이고 라인별 제어를 용이하게 합니다.
  3. 정규 표현식(Regex) 숙지: 복잡한 텍스트 패턴 매칭에는 정규 표현식이 매우 강력한 도구입니다. re 모듈의 사용법을 익히면 다양한 문자열 처리 문제를 효율적으로 해결할 수 있습니다.
  4. 모듈화된 코드 작성: 특정 기능을 수행하는 부분을 함수로 분리하여 코드의 재사용성을 높이고, 가독성 및 유지보수를 용이하게 만듭니다. (예: filter_and_copy_lines(input_path, output_path, regex_pattern))
  5. 예외 처리 강화: try-except 블록을 사용하여 파일이 없거나, 권한 문제 등 예외 상황에 대비하는 견고한 코드를 작성합니다.
  6. 인코딩 문제 고려: 텍스트 파일을 다룰 때는 항상 encoding 파라미터를 명시하여 인코딩 오류를 예방하는 것이 좋습니다. 일반적으로 "utf-8"이 널리 사용됩니다.
  7. 철저한 테스트: 다양한 시나리오(조건에 맞는 라인이 없는 경우, 파일이 비어있는 경우, 마지막 라인이 조건에 맞는 경우 등)에 대해 스크립트를 테스트하여 예상치 못한 동작을 방지합니다.

이 문서를 통해 파이썬을 이용한 파일 처리 로직 구현에 대한 이해를 높이고, 효과적인 문제 해결에 도움이 되기를 바랍니다. 궁금한 점이 있다면 언제든지 문의해주세요!

댓글 남기기