DiffMate

블로그로 돌아가기

파일 비교 시 글자가 깨질 때 해결법 (인코딩 가이드)

2025년 4월 1일

파일을 비교하려고 열었는데 한글이 깨져 보이거나, 정상적인 파일인데 비교 결과가 전부 "변경됨"으로 표시되는 경험을 해본 적 있으신가요? 대부분의 경우 인코딩 문제가 원인입니다.

이 글에서는 파일 비교 시 자주 발생하는 인코딩 문제의 원인과 해결법을 실제 사례와 함께 깊이 있게 설명합니다. 인코딩의 역사부터 실무 해결 팁까지 총정리합니다.

문자 인코딩의 역사: ASCII에서 UTF-8까지

문자 인코딩의 역사를 이해하면 왜 이런 문제가 생기는지 근본적으로 파악할 수 있습니다.

**ASCII (1963년)**: 컴퓨터 초창기에 만들어진 ASCII는 7비트로 128개의 문자를 표현했습니다. 영어 알파벳 대소문자, 숫자, 기본 특수문자만 다룰 수 있었고, 한글이나 한자 같은 비영어권 문자는 전혀 지원하지 않았습니다.

**ISO-8859 시리즈 (1987년)**: ASCII를 8비트로 확장하여 256개의 문자를 표현할 수 있게 되었습니다. ISO-8859-1(Latin-1)은 서유럽 언어를, ISO-8859-2는 중유럽 언어를 지원했습니다. 하지만 각 지역별로 서로 다른 표준을 사용했기 때문에, 여러 언어가 섞인 문서를 만들 수 없었습니다.

**EUC-KR과 지역별 인코딩 (1990년대)**: 한국은 EUC-KR, 일본은 Shift_JIS와 EUC-JP, 중국은 GB2312와 GBK를 각자 개발했습니다. EUC-KR은 KS X 1001 표준에 기반하여 2,350개의 한글 완성형 글자를 지원합니다. 이 시기에 만들어진 시스템과 파일들이 아직도 레거시로 남아 있어 현재까지 인코딩 문제를 일으킵니다.

**유니코드의 등장 (1991년)**: 전 세계 모든 문자를 하나의 체계로 통합하려는 프로젝트가 시작되었습니다. 유니코드는 각 문자에 고유한 코드 포인트(예: U+AC00 = '가')를 부여합니다. 현재 유니코드 15.0은 149,186개의 문자를 포함하며, 한글 11,172자를 완벽하게 지원합니다.

**UTF-8 (1993년)**: 유니코드를 실제 바이트로 저장하는 방법 중 가장 널리 쓰이는 인코딩입니다. 2024년 기준 전 세계 웹페이지의 98% 이상이 UTF-8을 사용합니다. 가변 길이 인코딩이라 ASCII 호환성을 유지하면서도 모든 유니코드 문자를 표현할 수 있습니다.

UTF-8의 동작 원리: 가변 길이 인코딩

UTF-8이 어떻게 작동하는지 알면 인코딩 문제를 디버깅할 때 큰 도움이 됩니다.

UTF-8은 문자에 따라 1~4바이트를 사용합니다:

  • **1바이트 (0xxxxxxx)**: ASCII 범위(U+0000~U+007F). 영어, 숫자, 기본 특수문자. 예: 'A' = 0x41
  • **2바이트 (110xxxxx 10xxxxxx)**: U+0080~U+07FF. 라틴 확장, 그리스어, 키릴 문자 등. 예: 'ü' = 0xC3 0xBC
  • **3바이트 (1110xxxx 10xxxxxx 10xxxxxx)**: U+0800~U+FFFF. 한글, 한자, 일본어 등 대부분의 아시아 문자. 예: '가' = 0xEA 0xB0 0x80
  • **4바이트 (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)**: U+10000~U+10FFFF. 이모지, 고대 문자, 수학 기호 등. 예: '😀' = 0xF0 0x9F 0x98 0x80

이 구조 덕분에 영어 텍스트는 ASCII와 동일한 크기를 유지하고, 한글 텍스트는 글자당 3바이트를 차지합니다. EUC-KR에서 한글이 2바이트인 것과 비교하면 파일 크기가 약 50% 커지지만, 범용성을 고려하면 UTF-8이 훨씬 유리합니다.

주요 인코딩 비교표

실무에서 자주 접하는 인코딩을 비교하면 다음과 같습니다:

**UTF-8**: 가변 1~4바이트. 전 세계 표준. 모든 유니코드 문자 지원. ASCII 호환. 웹, 최신 시스템의 기본 인코딩.

**UTF-16**: 가변 2~4바이트. Windows 내부, Java, .NET에서 사용. BOM으로 바이트 순서 지정 필요. Little Endian(LE)과 Big Endian(BE) 변형 존재.

**EUC-KR**: 가변 1~2바이트. 한국어 전용. 2,350자의 한글 완성형 지원. 구형 한국 웹사이트, 관공서 시스템에서 아직 사용.

**CP949 (MS949)**: EUC-KR의 마이크로소프트 확장. 11,172자의 한글 모두 지원. Windows 한국어 시스템의 기본 인코딩.

**GB2312 / GBK**: 중국어 간체 인코딩. GB2312는 6,763자, GBK는 21,886자 지원. 중국 레거시 시스템에서 사용.

**Shift_JIS**: 일본어 인코딩. 가변 1~2바이트. 일본 레거시 시스템에서 사용. 백슬래시/엔화 기호 문제로 유명.

**ISO-8859-1 (Latin-1)**: 1바이트 고정. 서유럽 언어 지원. 256자만 표현 가능. HTTP 기본 인코딩으로 역사적 의미.

증상별 원인 파악

"한글이 깨져 보인다" — 파일의 실제 인코딩과 프로그램이 해석하는 인코딩이 다를 때 발생합니다. EUC-KR로 저장된 파일을 UTF-8로 열면 한글이 2~3개의 이상한 문자로 바뀝니다. 예를 들어, "한글"이 "\xc7\xd1\xb1\xdb"로 저장되어 있는 EUC-KR 파일을 UTF-8로 읽으면 "ÇѱÛ" 같은 형태로 표시됩니다.

"파일 내용은 같은데 비교 결과가 다르다" — BOM(Byte Order Mark) 유무 차이일 수 있습니다. UTF-8 with BOM과 UTF-8 without BOM은 사람 눈에는 같지만 바이트 수준에서는 다릅니다. 또한 줄바꿈 문자 차이(Windows의 CRLF vs Unix의 LF)도 이런 증상을 유발합니다.

"특정 문자만 깨진다" — 일부 특수문자나 이모지가 해당 인코딩에서 지원되지 않는 경우입니다. EUC-KR은 기본 한글과 일부 특수문자만 지원하므로, '똠'이나 '믜' 같은 덜 사용되는 한글 조합이나, 이모지가 포함되면 깨집니다.

"파일 앞부분에 이상한 문자가 있다" — BOM이 문자로 표시되는 경우입니다. UTF-8 BOM(EF BB BF)이 텍스트로 해석되면 ''로 보입니다. 이는 CSV 파일을 프로그래밍 언어로 읽을 때 첫 번째 컬럼 이름 앞에 보이지 않는 문자가 붙어 파싱 오류를 일으키기도 합니다.

실무에서 겪는 인코딩 공포 사례

**사례 1 - 데이터베이스 마이그레이션**: 한 기업이 MySQL에서 PostgreSQL로 데이터를 마이그레이션하면서, MySQL의 latin1 테이블에 실제로는 EUC-KR 데이터가 저장되어 있었습니다. 중간에 UTF-8로 변환하면서 이중 인코딩(double encoding)이 발생하여 "한글" 같은 정상 데이터가 "íêµ" 같은 깨진 문자로 변환되었습니다. 수십만 건의 고객 데이터를 복구하는 데 2주가 소요되었습니다.

**사례 2 - API 데이터 교환**: 국내 관공서 API에서 EUC-KR로 응답을 보내는데, 프론트엔드에서 UTF-8로 해석하여 모든 한글 데이터가 깨지는 문제가 있었습니다. Content-Type 헤더에 charset이 명시되지 않아 클라이언트가 기본값인 UTF-8로 해석한 것이 원인이었습니다.

**사례 3 - CSV 파일 합병**: 여러 부서에서 제출한 CSV 파일을 합치는 과정에서, A 부서는 UTF-8, B 부서는 EUC-KR, C 부서는 UTF-8 with BOM으로 저장한 파일을 보냈습니다. 단순히 파일을 이어붙였더니 중간부터 글자가 깨지고, BOM 문자가 데이터 중간에 삽입되는 문제가 발생했습니다.

**사례 4 - Git 저장소 인코딩 혼재**: 팀원 중 일부가 Windows에서 CP949로 소스코드 주석을 작성하고, 나머지가 macOS에서 UTF-8로 작성하면서 git diff에서 한글 주석이 전부 변경된 것으로 표시되었습니다. .gitattributes에 인코딩 설정을 하지 않아 발생한 문제입니다.

해결 방법 1: 파일 인코딩 확인

먼저 파일의 실제 인코딩을 확인하세요.

**텍스트 에디터 사용**: VS Code에서 파일을 열면 우측 하단에 인코딩이 표시됩니다. 클릭하면 "Reopen with Encoding" 옵션으로 다른 인코딩으로 다시 열어볼 수 있습니다. Notepad++에서는 "인코딩" 메뉴에서 현재 인코딩을 확인할 수 있습니다.

**명령줄 도구 사용**: macOS/Linux에서 file 명령어로 인코딩을 확인할 수 있습니다:

``` file -bi document.txt # 출력 예: text/plain; charset=utf-8 ```

Python의 chardet 라이브러리를 사용하면 더 정확한 감지가 가능합니다:

``` pip install chardet chardetect document.txt # 출력 예: document.txt: EUC-KR with confidence 0.99 ```

uchardet는 Mozilla의 인코딩 감지 라이브러리를 기반으로 한 명령줄 도구입니다:

``` brew install uchardet # macOS uchardet document.txt # 출력 예: EUC-KR ```

해결 방법 2: 인코딩 변환

인코딩이 다른 두 파일을 비교할 때는 먼저 하나의 인코딩으로 통일해야 합니다. UTF-8이 가장 범용적이므로 UTF-8로 변환하는 것을 권장합니다.

**명령줄 iconv 사용**:

``` iconv -f EUC-KR -t UTF-8 input.txt > output.txt ```

**Python으로 변환**:

``` with open('input.txt', 'r', encoding='euc-kr') as f: content = f.read() with open('output.txt', 'w', encoding='utf-8') as f: f.write(content) ```

**Node.js에서 iconv-lite 사용**:

``` const iconv = require('iconv-lite'); const fs = require('fs'); const buffer = fs.readFileSync('input.txt'); const content = iconv.decode(buffer, 'euc-kr'); fs.writeFileSync('output.txt', iconv.encode(content, 'utf-8')); ```

대량의 파일을 일괄 변환해야 할 경우, 쉘 스크립트를 작성하여 디렉토리 내 모든 파일을 순회하며 변환하는 방법도 있습니다.

해결 방법 3: BOM 심층 분석

BOM(Byte Order Mark)은 파일의 인코딩과 바이트 순서를 나타내는 특수 마커입니다.

**BOM의 종류**: - UTF-8 BOM: EF BB BF (3바이트) - UTF-16 LE BOM: FF FE (2바이트) - UTF-16 BE BOM: FE FF (2바이트) - UTF-32 LE BOM: FF FE 00 00 (4바이트) - UTF-32 BE BOM: 00 00 FE FF (4바이트)

**BOM이 도움이 되는 경우**: Windows 환경에서 UTF-8 파일을 Excel로 열 때 BOM이 있어야 한글이 정상 표시됩니다. UTF-16 파일에서는 BOM이 바이트 순서를 알려주는 필수 요소입니다.

**BOM이 문제를 일으키는 경우**: PHP, Python 등의 스크립트 파일 앞에 BOM이 있으면 예상치 못한 출력이 발생합니다. JSON 파일에 BOM이 있으면 파싱 에러가 발생할 수 있습니다. 쉘 스크립트의 shebang(#!/bin/bash) 앞에 BOM이 있으면 실행이 안 됩니다. CSV 파일을 프로그래밍 언어로 읽을 때 첫 번째 필드 앞에 보이지 않는 문자가 붙습니다.

**BOM 제거 방법**:

``` # Linux/macOS에서 sed 사용 sed -i '1s/^\xEF\xBB\xBF//' file.txt

# Python 사용 with open('file.txt', 'r', encoding='utf-8-sig') as f: content = f.read() with open('file.txt', 'w', encoding='utf-8') as f: f.write(content) ```

**BOM 추가 방법** (Excel용 CSV 등):

``` # Python에서 BOM 포함 UTF-8로 저장 with open('file.csv', 'w', encoding='utf-8-sig') as f: f.write(content) ```

Excel의 인코딩 함정

Excel은 인코딩 문제의 가장 대표적인 원인 중 하나입니다.

**CSV 내보내기 문제**: Excel에서 "CSV로 저장"하면 Windows 시스템 기본 인코딩(한국어 Windows에서는 CP949)으로 저장됩니다. "CSV UTF-8 (쉼표로 분리)" 옵션을 선택해야 UTF-8 BOM 포함으로 저장됩니다. 이 두 옵션의 차이를 모르면 다른 시스템에서 파일을 열 때 글자가 깨집니다.

**CSV 열기 문제**: Excel에서 CSV를 더블클릭으로 열면 시스템 기본 인코딩으로 해석합니다. UTF-8 파일이지만 BOM이 없으면 한글이 깨질 수 있습니다. 해결법은 "데이터" 탭 → "텍스트에서" → 파일 선택 → 인코딩을 "65001: 유니코드(UTF-8)"로 지정하는 것입니다.

**숨겨진 BOM 문제**: Excel에서 UTF-8 CSV로 저장하면 BOM이 자동 추가됩니다. 이 파일을 Linux 서버에 업로드하면 첫 번째 컬럼 이름 앞에 보이지 않는 BOM 문자가 포함되어, 프로그램에서 컬럼을 찾지 못하는 문제가 발생합니다.

크로스 플랫폼 인코딩 함정

Windows, macOS, Linux 간에는 인코딩뿐 아니라 줄바꿈 문자도 다릅니다.

**줄바꿈 문자 차이**: - Windows: CRLF (\r\n, 0x0D 0x0A) - macOS/Linux: LF (\n, 0x0A) - 구형 Mac OS: CR (\r, 0x0D)

이 차이 때문에 Windows에서 만든 파일을 macOS에서 비교하면, 모든 줄이 "변경됨"으로 표시될 수 있습니다. 내용은 같지만 줄 끝의 보이지 않는 문자가 다르기 때문입니다.

**파일 이름 인코딩**: macOS는 파일 이름에 NFD(분해형) 유니코드 정규화를 사용하고, Windows와 Linux는 NFC(조합형)를 사용합니다. 한글 파일 이름 "가.txt"가 OS에 따라 다른 바이트 시퀀스로 저장되어, 압축 파일을 풀거나 네트워크 드라이브에서 파일을 열 때 이름이 깨질 수 있습니다.

**기본 인코딩 차이**: Windows 한국어판은 기본 인코딩이 CP949이고, macOS와 Linux는 UTF-8입니다. 메모장(Notepad)에서 "새 파일"을 만들면 Windows 10 이전 버전에서는 CP949로 저장되었습니다. Windows 10 이후에는 기본값이 UTF-8로 변경되었지만, 여전히 구형 애플리케이션들은 CP949를 사용합니다.

해결 방법 4: DiffMate의 자동 인코딩 감지

DiffMate는 파일을 열 때 자동으로 인코딩을 감지하는 캐스케이드 방식을 사용합니다. 구체적인 동작 방식은 다음과 같습니다:

**1단계 - BOM 감지**: 파일의 첫 바이트를 확인하여 BOM이 있는지 검사합니다. BOM이 발견되면 해당 인코딩으로 즉시 결정합니다. UTF-8 BOM(EF BB BF), UTF-16 LE BOM(FF FE), UTF-16 BE BOM(FE FF)을 순서대로 확인합니다.

**2단계 - UTF-8 시도**: BOM이 없으면 UTF-8로 디코딩을 시도합니다. UTF-8은 바이트 패턴이 엄격하게 정의되어 있어, 유효하지 않은 바이트 시퀀스가 발견되면 다음 인코딩으로 넘어갑니다.

**3단계 - EUC-KR 시도**: UTF-8이 실패하면 EUC-KR(CP949)로 시도합니다. 한국어 환경에서 가장 흔한 레거시 인코딩이기 때문에 두 번째 우선순위입니다.

**4단계 - ISO-8859-1 폴백**: EUC-KR도 실패하면 ISO-8859-1을 시도합니다. 이 인코딩은 모든 바이트 값(0x00-0xFF)을 유효한 문자로 매핑하므로 절대 실패하지 않습니다. 다만 한글은 올바르게 표시되지 않습니다.

**5단계 - UTF-16 시도**: 앞선 방법으로 모두 의미 있는 텍스트가 나오지 않으면 UTF-16으로 시도합니다.

이 캐스케이드 방식 덕분에 사용자가 인코딩을 수동으로 지정할 필요 없이, 대부분의 파일을 올바르게 열어 비교할 수 있습니다.

프로그래밍 언어별 인코딩 처리

**Python**: chardet 또는 cchardet 라이브러리를 사용하여 인코딩을 자동 감지할 수 있습니다. Python 3는 기본적으로 문자열이 유니코드이므로, open() 함수에서 encoding 파라미터를 명시하는 것이 좋습니다. 'utf-8-sig'를 사용하면 BOM이 있어도 자동으로 제거합니다.

**Node.js**: iconv-lite 패키지가 가장 널리 사용되며, EUC-KR, Shift_JIS, GB2312 등 다양한 인코딩을 지원합니다. jschardet 패키지로 인코딩 감지도 가능합니다. Buffer 객체의 toString() 메서드는 UTF-8만 지원하므로, 다른 인코딩의 파일을 읽을 때는 반드시 iconv-lite를 사용해야 합니다.

**Java**: InputStreamReader의 생성자에 Charset을 지정합니다. Java는 내부적으로 UTF-16을 사용하므로, 파일 I/O 시 항상 인코딩을 명시하는 것이 좋습니다.

예방을 위한 실무 팁

  • 새로 만드는 파일은 항상 UTF-8(BOM 없이)로 저장
  • 팀 내 인코딩 표준을 정하고 .editorconfig 파일로 공유
  • Git 저장소에 .gitattributes 파일을 추가하여 줄바꿈 문자 자동 변환 설정
  • 외부에서 받은 파일은 비교 전 인코딩 확인
  • 데이터베이스에서 추출 시 UTF-8 옵션 사용
  • CSV 파일을 교환할 때는 인코딩을 문서화
  • CI/CD 파이프라인에 인코딩 검증 단계 추가
  • IDE 설정에서 기본 인코딩을 UTF-8로 통일
  • 오래된 시스템과 연동할 때는 인코딩 변환 레이어를 명시적으로 구현

결론

인코딩 문제는 원인만 알면 체계적으로 해결할 수 있습니다. ASCII에서 UTF-8까지의 역사를 이해하고, 각 인코딩의 특성을 파악하면 대부분의 문제를 빠르게 진단할 수 있습니다. 파일 비교 전에 인코딩을 확인하고 통일하는 습관을 들이면 불필요한 시간 낭비를 줄일 수 있습니다. DiffMate의 자동 인코딩 감지 캐스케이드를 활용하면, 인코딩이 다른 파일도 별도 변환 없이 바로 비교할 수 있어 업무 효율이 크게 향상됩니다.

DiffMate로 파일 비교하기