분명히 잘 저장한 파일인데 열어 보면 占쏙옙이나 ¾È³çÇϼ¼¿ä 같은 글자가 늘어서 있습니다. 파이썬을 돌리면 UnicodeDecodeError가 나고, 엑셀로 CSV를 열면 이름 칸이 통째로 외계어가 됩니다. 윈도우에서 개발하는 국내 사용자라면 한 번쯤은 겪는 한글 깨짐 인코딩 오류입니다. 짜증나는 건 매번 다른 증상으로 나타나서, 지난번에 통했던 방법이 이번엔 안 듣는다는 점입니다. 이 글에서는 증상만 보고 원인을 역추적하는 방법과, 파이썬·파워셸·엑셀 각각에서의 확실한 해결책을 정리합니다.
원인은 하나입니다 — 저장한 규칙과 읽는 규칙이 다르다
컴퓨터는 글자를 숫자(바이트)로 바꿔서 저장합니다. 이 변환 규칙이 인코딩입니다. 한국어 윈도우에는 오래된 규칙과 새 규칙이 섞여 있습니다.
| 인코딩 | 한글 1글자 | 어디서 쓰이나 |
|---|---|---|
| CP949 (=확장 완성형, EUC-KR의 확장) | 2바이트 | 윈도우 한국어판의 기본값. 메모장 “ANSI”, 구형 프로그램, 관공서 다운로드 파일 |
| UTF-8 | 3바이트 | 웹, 깃허브, 리눅스, 맥, 요즘 개발 도구 전부 |
| UTF-8 with BOM | 3바이트 + 앞머리 3바이트 | 엑셀과 Windows PowerShell 5.1이 요구하는 형태 |
UTF-8로 저장한 파일을 CP949로 읽으면 바이트가 엉뚱하게 두 개씩 짝지어지면서 한자와 낯선 한글이 튀어나옵니다. 그 유명한 占쏙옙이 대표적인데, 이건 사실 “읽을 수 없는 글자”를 뜻하는 기호(U+FFFD)의 UTF-8 바이트 EF BF BD가 두 번 반복된 걸 CP949가 EF BF → 占, BD EF → 쏙, BF BD → 옙 으로 잘못 끊어 읽은 결과입니다. 원인을 알고 나면 증상이 곧 단서가 됩니다.
증상으로 원인 찾기
파일을 열었을 때 보이는 모양만으로 어느 방향이 어긋났는지 거의 특정할 수 있습니다.
| 화면에 보이는 것 | 실제 상황 | 해결 방향 |
|---|---|---|
| 占쏙옙, 뷁, 삼각형·한자 뒤섞임 | UTF-8 파일을 CP949로 읽는 중 | 읽는 쪽에 encoding="utf-8" 지정 |
| ¾È³çÇϼ¼¿ä 처럼 유럽 문자 | CP949 파일을 UTF-8/Latin-1로 읽는 중 | 읽는 쪽에 encoding="cp949" 지정 |
| 안녕하세요 | UTF-8 파일을 서유럽 인코딩으로 읽는 중 | 브라우저·에디터 인코딩을 UTF-8로 변경 |
| ???? 또는 □□□□ | 저장할 때 이미 손실됨 (되돌릴 수 없음) | 원본을 다시 받아서 저장 단계부터 수정 |
| 첫 줄 맨 앞에  가 붙음 | BOM을 글자로 읽는 중 | utf-8-sig로 읽거나 BOM 없이 저장 |
| UnicodeDecodeError 예외 | 읽기 자체가 실패 | 아래 파이썬 절 참고 |
물음표(????)가 보이면 그 파일은 포기하세요. 위 다섯 가지는 읽는 방법만 바꾸면 원문이 되살아나지만, 물음표는 저장하는 순간 해당 글자를 표현할 수 없어 버려진 상태입니다. 남은 건 물음표뿐이라 어떤 도구로도 복구되지 않습니다. 원본 파일을 다시 구하는 것 외에 방법이 없습니다.
파이썬 — encoding을 생략하지 않는 것이 8할
가장 흔한 원인은 open()에서 인코딩을 생략한 것입니다. 이때 파이썬은 운영체제 기본값을 쓰는데, 한국어 윈도우에서는 그게 CP949입니다. 같은 코드를 맥이나 리눅스, 깃허브 액션에서 돌리면 UTF-8이라 잘 되고, 내 윈도우에서만 터집니다.
# 깨지는 코드 — 윈도우에서 인코딩을 생략하면 CP949로 읽습니다
with open("data.csv") as f:
text = f.read()
# 고치는 코드 — 항상 명시합니다
with open("data.csv", encoding="utf-8") as f:
text = f.read()
지금 내 환경이 무엇을 기본값으로 쓰는지는 이렇게 확인합니다.
import locale, sys
print("기본 인코딩 :", locale.getpreferredencoding(False))
print("표준출력 :", sys.stdout.encoding)
print("파일시스템 :", sys.getfilesystemencoding())
# 윈도우 한국어 환경 출력 예시
# 기본 인코딩 : cp949
# 표준출력 : utf-8
# 파일시스템 : utf-8
프로젝트 전체를 한 번에 UTF-8로 맞추고 싶다면 환경변수 PYTHONUTF8=1을 켜거나 python -X utf8 script.py로 실행합니다. 파이썬 3.7부터 쓸 수 있는 UTF-8 모드로, 인코딩을 생략한 open()까지 전부 UTF-8로 동작하게 만듭니다. 다만 이건 내 실행 환경만 바꾸는 것이라, 다른 사람이 받아서 돌릴 코드라면 결국 encoding=을 코드에 직접 적어 두는 편이 안전합니다.
파일 인코딩을 모를 때
받아온 파일의 인코딩이 무엇인지 모르겠다면 추측하지 말고 탐지 라이브러리를 씁니다.
# 이 파일이 대체 무슨 인코딩인지 모를 때
# pip install charset-normalizer
from charset_normalizer import from_path
result = from_path("의문의파일.txt").best()
print(result.encoding) # 예: utf_8, cp949, utf_8_sig
print(str(result)[:200]) # 앞부분만 미리보기
탐지 결과를 확인했다면, 이후 작업이 편하도록 아예 UTF-8로 바꿔 두는 게 좋습니다.
# CP949로 저장된 파일을 UTF-8로 일괄 변환
from pathlib import Path
for path in Path("logs").glob("*.txt"):
raw = path.read_bytes()
try:
text = raw.decode("utf-8")
continue # 이미 UTF-8이면 건너뜀
except UnicodeDecodeError:
text = raw.decode("cp949", errors="replace")
path.write_text(text, encoding="utf-8")
print("변환:", path)
errors="replace"는 못 읽는 글자를 물음표 기호로 대체하고 진행합니다. 로그처럼 일부 손실을 감수하고라도 끝까지 읽어야 하는 파일에 적합합니다. 반대로 결제 데이터나 회원 명단처럼 한 글자도 틀리면 안 되는 파일에는 절대 쓰지 마세요. 조용히 망가진 데이터가 통과해 버립니다.
엑셀에서 CSV가 깨질 때 — BOM 세 바이트
파이썬으로 만든 CSV를 엑셀에서 더블클릭해 열면 한글이 깨지는데, 같은 파일을 메모장이나 VS Code로 열면 멀쩡한 경우가 있습니다. 이건 파일이 잘못된 게 아니라 엑셀이 “UTF-8이라는 표시가 없으면 CP949로 간주”하기 때문입니다. 그 표시가 BOM(Byte Order Mark)이고, 파일 맨 앞에 붙는 EF BB BF 3바이트입니다.
# 엑셀에서 두 번 클릭해 열어도 안 깨지는 CSV
import csv
rows = [("이름", "부서"), ("김민수", "개발팀")]
with open("직원.csv", "w", newline="", encoding="utf-8-sig") as f:
csv.writer(f).writerows(rows)
# pandas 라면
# df.to_csv("직원.csv", index=False, encoding="utf-8-sig")
utf-8이 아니라 utf-8-sig인 점만 다릅니다. 반대로 BOM이 붙은 파일을 읽을 때도 utf-8-sig로 읽어야 첫 줄 맨 앞에 가 섞여 들어오지 않습니다. 첫 번째 컬럼명만 유독 조회가 안 된다면 십중팔구 이 문제입니다.
파워셸 — BOM이 없으면 조용히 실패합니다
Windows PowerShell 5.1(윈도우에 기본 설치된 파란 창)은 BOM 없는 UTF-8 스크립트를 CP949로 읽습니다. 스크립트 안의 한글 주석과 경로가 전부 깨지는데, 문법 오류가 아니라 문자열 내용이 깨지는 것이라 에러 없이 그냥 잘못 동작합니다.
실제로 겪은 사고입니다. 자동 발행 스크립트를 편집하고 BOM 없이 저장했더니, 한글이 포함된 경로를 찾지 못해 아무 일도 하지 않은 채 종료 코드 0으로 끝났습니다. 작업 스케줄러에는 “성공”으로 기록됐고, 로그 폴더조차 생기지 않아 하루가 지나서야 알아챘습니다. 파워셸 스크립트에서 인코딩 문제는 시끄럽게 터지는 게 아니라 조용히 실패합니다.
지금 파일에 BOM이 있는지는 첫 3바이트를 찍어 보면 됩니다.
# 첫 3바이트가 EF BB BF 면 BOM 이 있는 UTF-8입니다
Get-Content .\daily-publish.ps1 -Encoding Byte -TotalCount 3 |
ForEach-Object { "{0:X2}" -f $_ }
# 출력: EF / BB / BF → 정상
# 출력: 23 / 20 / EC → BOM 없음, 5.1에서 깨질 수 있음
BOM이 없다면 다시 저장합니다. 버전에 따라 옵션 이름이 다르니 주의하세요.
# 이미 있는 파일을 BOM 붙은 UTF-8로 다시 저장 (PowerShell 5.1)
$text = Get-Content .\daily-publish.ps1 -Raw -Encoding UTF8
Set-Content .\daily-publish.ps1 -Value $text -Encoding UTF8
# PowerShell 7 이상은 utf8 이 BOM 없는 UTF-8이므로 이름이 다릅니다
# Set-Content .\daily-publish.ps1 -Value $text -Encoding utf8BOM
| 버전 | -Encoding utf8 의 의미 | BOM 붙이려면 |
|---|---|---|
| Windows PowerShell 5.1 | BOM 있는 UTF-8 | -Encoding utf8 그대로 |
| PowerShell 7 이상 | BOM 없는 UTF-8 | -Encoding utf8BOM |
덧붙여, Set-Content와 Add-Content는 인코딩을 생략하면 시스템 기본 코드페이지(한국어 윈도우에서는 CP949)로 씁니다. 다른 도구가 읽을 파일을 만들 때는 -Encoding을 매번 명시하는 습관을 들이는 편이 좋습니다.
콘솔 출력만 깨질 때
파일은 멀쩡한데 터미널에 찍히는 한글만 깨지는 경우도 있습니다. 이건 콘솔 코드페이지 문제라 접근이 다릅니다.
chcp를 입력해 현재 코드페이지를 확인합니다.949면 CP949입니다.chcp 65001로 UTF-8로 바꾼 뒤 다시 실행해 봅니다.- 해결되면 영구 적용을 고민합니다. 다만
chcp는 창을 닫으면 초기화됩니다. - 근본적으로는 Windows Terminal을 쓰는 편이 낫습니다. 기본이 UTF-8이라 이 문제 자체가 거의 없습니다.
윈도우 설정에도 시스템 전체를 UTF-8로 바꾸는 옵션이 있습니다. 제어판 → 국가 또는 지역 → 관리자 옵션 → 시스템 로캘 변경에서 “세계 언어 지원을 위해 Unicode UTF-8 사용(베타)”을 켜는 방식입니다. 다만 이름에 베타가 붙은 이유가 있습니다. CP949를 전제로 만들어진 구형 국내 프로그램들이 이 상태에서 오히려 깨질 수 있어서, 개발용 PC가 아니라면 권하지 않습니다.
깃에서 한글 파일명이 이상하게 보일 때
git status에서 한글 파일명이 "\355\225\234\352\270\200.txt" 같은 숫자로 나오는 건 깨진 게 아닙니다. 깃이 ASCII 밖의 문자를 8진수로 이스케이프해 보여주는 기본 동작이고, 파일은 멀쩡합니다. 보기 불편하다면 끄면 됩니다.
git config --global core.quotepath false
다시 겪지 않기 위한 체크리스트
open()과Set-Content에서 인코딩을 절대 생략하지 않습니다. 생략하면 실행하는 컴퓨터마다 결과가 달라집니다.- 에디터 기본 저장 인코딩을 UTF-8로 고정합니다. VS Code는 설정에서
files.encoding을utf8로 둡니다. - 엑셀로 열릴 CSV는
utf-8-sig, 파워셸 5.1 스크립트는 BOM 있는 UTF-8. 이 둘만 예외로 기억합니다. errors="ignore"는 쓰지 않습니다. 못 읽은 글자를 말없이 버려서, 나중에 데이터가 왜 비었는지 추적할 단서가 남지 않습니다.- 자동화 스크립트를 편집했다면 실행 결과가 “성공”이어도 실제 산출물까지 눈으로 확인합니다. 인코딩 오류는 종료 코드를 올리지 않는 경우가 많습니다.
마무리
당장 할 일은 하나입니다. 지금 작업 중인 프로젝트에서 open(을 검색해 보세요. encoding=이 빠진 줄이 하나라도 있다면 거기가 다음에 터질 자리입니다. 파일 인코딩을 통일하는 것보다 읽고 쓰는 코드에 규칙을 명시하는 것이 훨씬 확실한 대책입니다.
다음 글에서는 흩어져 있는 CSV·엑셀 파일 여러 개를 파이썬으로 한 번에 병합하는 방법을 다룹니다. 오늘 정리한 인코딩 지식이 그대로 쓰이는 작업입니다.
“한글 깨짐 오류 해결 총정리 — CP949와 UTF-8, BOM까지”에 대한 1개의 생각