오래된 SVN 또는 NAS 환경의 데이터를 FTP/SFTP로 이관하는 과정에서 인코딩 설정 오류로 인해 파일이 깨지는 문제를 경험하여 복원하는 방법을 작성해봤습니다.
10만여 건의 대량 파일 환경에서 **실질적 복원율 100%**를 달성한 사전 조사, 인코딩 매칭, 전환 및 검수 프로세스를 정리한 가이드입니다.
전체적 확인이 어려운 저저되던 10년 이상 된 레거시 파일을 새로운 UTF-8 기반 시스템으로 마이그레이션해야 했습니다.
단순 Dump & Restore 방식으로는 한글 텍스트가 모두 깨지거나 열리지 않는 현상이 있었습니다.
iconv 유틸리티를 사용하여 덤프 파일 전체를 변환 시도했으나, 일부 특수문자에서 변환 오류가 발생하며 프로세스가 중단되었습니다.수백만 건의 데이터를 손실 없이(특수문자 일부 제외) 성공적으로 UTF-8 환경으로 이전 완료했습니다. 변환 실패 로그를 통해 이후 수작업 검수까지 원활하게 진행할 수 있었습니다.
대량의 파일 처리 시 OS 자체의 파일 시스템 제한을 고려해야 합니다.
참고: 최근 도구나 언어의 호환성이 좋아져 자동 변환이 지원되지만, 자동 처리가 불가능한 특수 케이스에서만 본 가이드의 탐색 및 전환 명령어를 활용하는 것을 권장합니다.
전환 작업 전, 숨김 항목을 제외한 전체 파일의 확장자 분포와 인코딩 종류를 파악합니다.
5.1 확장자별 파일 개수 확인
find . -type f -name "*.*" -not -path "*/.*" | awk -F. '{print $NF}' | sort | uniq -c | sort -nr5.2 존재하는 인코딩 종류 분석
find . -type f -name "*.*" -not -path "*/.*" -exec file -i {} + | awk -F'charset=' '{print $2}' | sort -u중간 파일 생성 없이 파일 탐색부터 인코딩 판별, UTF-8 전환까지 단일 프로세스로 처리하는 파이썬 스크립트입니다.
charset-normalizer를 활용하여 판별 정확도를 극대화했습니다.
pip install charset-normalizer# converter.py
# datetime.py 등과 이름 충돌을 피하기 위해 converter.py로 작성
import os
from pathlib import Path
from charset_normalizer import from_path
# 변환 대상 인코딩 매핑 테이블 (소문자 기준 정렬)
ENCODING_MAP = {
'utf-8': 'utf-8',
'ascii': 'utf-8', # 아스키는 UTF-8과 호환되므로 변환 불필요
'euc-kr': 'euc-kr',
'cp949': 'cp949',
'ms949': 'cp949', # 파이썬 표준 명칭은 cp949
'iso-8859-1': 'iso-8859-1',
'latin-1': 'iso-8859-1',
'shift_jis': 'shift_jis',
'sjis': 'shift_jis',
'gb2312': 'gbk', # 깨짐 방지를 위해 상위 호환인 gbk로 매핑
'gbk': 'gbk'
}
def is_hidden(path: Path) -> bool:
"""경로 중에 숨김 폴더나 숨김 파일(.으로 시작)이 포함되어 있는지 검사합니다."""
return any(part.startswith('.') for part in path.parts)
def find_target_files(root_dir: str):
"""지정한 디렉터리 하위에서 숨김 항목을 제외한 모든 파일을 탐색합니다."""
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
file_path = Path(dirpath) / filename
# 숨김 파일/폴더 제외 및 확장자가 있는 파일만 필터링
if not is_hidden(file_path) and file_path.suffix:
yield file_path
def convert_to_utf8(file_path: Path):
"""파일의 현재 인코딩을 분석하여 UTF-8로 정밀 변환합니다."""
try:
# 1. 파일 인코딩 정밀 분석
matches = from_path(file_path)
best_match = matches.best()
if not best_match:
print(f"[제외] 인코딩 판별 불가 (바이너리 가능성): {file_path}")
return False
detected_encoding = best_match.encoding.lower()
# 2. 매핑 테이블에서 표준 명칭 찾기
matched_encoding = None
for key, value in ENCODING_MAP.items():
if key in detected_encoding:
matched_encoding = value
break
# 매핑되지 않은 기타 인코딩이거나 이미 UTF-8/ASCII인 경우 건너뜀
if not matched_encoding or matched_encoding == 'utf-8':
return False
# 3. 변환 진행
content = best_match.output(encoding=matched_encoding)
# UTF-8로 덮어쓰기
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"[변환성공] {file_path} ({detected_encoding} -> UTF-8)")
return True
except Exception as e:
print(f"[실패] 에러 발생 {file_path}: {e}")
return False
def main():
root_directory = "." # 현재 디렉터리 기준 탐색
print("=== 파이썬 인코딩 변환 작업 시작 ===")
total_count = 0
converted_count = 0
for file_path in find_target_files(root_directory):
total_count += 1
if file_path.name == "converter.py":
continue
if convert_to_utf8(file_path):
converted_count += 1
print(f"\n=== 작업 완료 | 총 탐색 파일: {total_count}개 / 변환 완료: {converted_count}개 ===")
if __name__ == "__main__":
main()매핑하지 않은 인코딩(예: binary)을 제외하는 이유
binary 파일(이미지, 영상, 압축파일, 컴파일된.class파일 등)은 시스템이 직접 처리하는 데이터 덩어리입니다.
텍스트 인코딩인 UTF-8로 강제 변환 시 데이터가 영구적으로 손상되어 파일이 파손됩니다.
따라서 텍스트가 아닌 파일은 본래 고유 인코딩을 유지하고, 목적에 맞는 전용 뷰어나 도구(Tools)를 활용해야 합니다.
| 비교 항목 | Linux CLI + Java 조합 | Python 통합 스크립트 |
|---|---|---|
| 순수 코드 속도 | 매우 빠름 (우세) | 보통 |
| 디스크 I/O 횟수 | 많음 (목록 파일 생성 후 변환 시 재접근) | 적음 (파일당 단 1회 접근) |
| 작업 단계 | 복잡함 (명령어 실행 ➡️ 파일 분할 ➡️ 자바 실행) | 간편함 (스크립트 1회 실행으로 종료) |
| 인코딩 판별 정확도 | 보통 (file -i 명령어의 간헐적 오판) | 높음 (charset-normalizer 바이트 분석) |
인코딩 초기 설정의 중요성
데이터 저장 형식은 초기 설정이 가장 중요하며, 파일의 목적성(바이너리 vs 텍스트)을 무시한 무분별한 변환은 지양해야 합니다.
언어별 효율성 체감
순수 처리 속도는 컴파일 언어인 Java가 우수하지만, 대량 마이그레이션 시 파이프라인 단계를 줄이고 디스크 I/O를 최소화하는 측면에서는 Python 같은 인터프리터 언어가 최종 작업 효율 면에서 훨씬 유리함을 확인했습니다.
최종 성과
본 아키텍처 흐름을 통해 전체 데이터의 ~96%를 안정적으로 복원했으며, 나머지 ~4%(임시 temp 파일, 빈 파일 등 변환 불필요 항목)를 유효하게 격리함으로써 실질적 100% 전환 처리 성공을 달성했습니다.