developer
엑셀에서 CSV 한글이 깨질 때: RFC 4180 규칙과 인코딩 해결법
쉼표와 따옴표가 들어간 셀은 어떻게 저장되는지, UTF-8 CSV가 엑셀에서 깨지는 이유와 BOM, CP949 변환까지. CSV를 다룰 때 알아야 할 규칙을 예시로 정리합니다.
modu.tools Editorial · ·
developer
쉼표와 따옴표가 들어간 셀은 어떻게 저장되는지, UTF-8 CSV가 엑셀에서 깨지는 이유와 BOM, CP949 변환까지. CSV를 다룰 때 알아야 할 규칙을 예시로 정리합니다.
modu.tools Editorial · ·
CSV는 가장 단순한 데이터 형식처럼 보입니다. 값 사이에 쉼표를 넣고 줄을 바꾸면 끝이니까요. 그런데 실무에서는 CSV 때문에 생기는 문제가 의외로 많습니다. 엑셀로 열었더니 한글이 ’?꾪솕’처럼 깨지고, 주소에 들어간 쉼표 때문에 열이 밀리고, 전화번호 앞의 0이 사라집니다. 이 글은 CSV의 표준 규칙인 RFC 4180을 바탕으로 이런 문제가 왜 생기는지, 어떻게 피하는지 정리합니다.
RFC 4180은 2005년에 CSV의 공통 형식을 정리한 문서입니다. 엄밀한 의미의 강제 표준이라기보다 “대부분의 프로그램이 이렇게 한다”를 문서로 남긴 것이지만, 서로 다른 프로그램끼리 CSV를 주고받을 때 기준으로 삼기 좋습니다. 핵심 규칙은 다음과 같습니다.
6번과 7번이 실무에서 가장 중요합니다.
다음 표를 CSV로 저장한다고 해 봅시다.
| 이름 | 주소 | 메모 |
|---|---|---|
| 김모두 | 서울시 강남구, 테헤란로 1 | 문 앞에 “부재 시” 두기 |
주소에 쉼표가, 메모에 큰따옴표가 들어 있습니다. RFC 4180에 맞게 저장하면 이렇게 됩니다.
이름,주소,메모
김모두,"서울시 강남구, 테헤란로 1","문 앞에 ""부재 시"" 두기"
주소는 쉼표 때문에 따옴표로 감쌌고, 메모는 안의 큰따옴표를 두 번씩 썼습니다. 만약 이 규칙을 무시하고 단순히 쉼표로만 나누는 코드를 쓰면 주소가 ‘서울시 강남구’와 ’ 테헤란로 1’ 두 열로 쪼개집니다. 직접 split(",")으로 CSV를 파싱하면 안 되는 이유입니다.
셀 안에 줄바꿈이 있는 경우도 마찬가지로 따옴표 안에 그대로 넣습니다. 이 때문에 CSV 파일의 ’줄 수’와 ’레코드 수’는 다를 수 있습니다.
CSV 파일 자체에는 “나는 UTF-8이다” 같은 정보가 들어 있지 않습니다. 그래서 파일을 여는 프로그램이 인코딩을 추측해야 하는데, 이 추측이 틀리면 한글이 깨집니다.
한국어 환경에서 흔히 만나는 경우는 두 가지입니다.
UTF-8로 저장된 CSV를 프로그램이 CP949로 해석하면 3바이트짜리 한글을 2바이트씩 잘못 끊어 읽어 엉뚱한 글자가 나옵니다. 반대로 CP949 파일을 UTF-8로 읽으면 유효하지 않은 바이트가 많아 ’�’로 가득 찹니다. 인코딩별 바이트 차이는 한글 바이트 계산 가이드에서 자세히 다뤘습니다.
1. UTF-8 BOM 붙이기. 파일 맨 앞에 EF BB BF 세 바이트(BOM)를 넣으면 엑셀을 비롯한 많은 프로그램이 UTF-8로 인식합니다. 웹에서 CSV 다운로드 기능을 만들 때 가장 흔히 쓰는 방법입니다. 단, 일부 프로그램은 BOM을 첫 열 이름의 일부로 읽어 버리기 때문에, 받는 쪽이 코드라면 BOM을 제거하고 읽어야 합니다.
2. 가져오기 기능으로 열기. 엑셀에서 파일을 더블클릭해 여는 대신 데이터 가져오기 기능을 쓰면 인코딩을 직접 고를 수 있습니다. 받는 사람에게 이 방법을 안내하는 것도 방법입니다.
3. CP949로 변환해 저장하기. 받는 사람이 오래된 시스템을 쓴다면 CP949로 변환해 주는 것이 확실합니다. 다만 CP949로 표현할 수 없는 이모지나 일부 특수 문자는 손실되니 변환 전에 확인하세요.
인코딩만큼 흔한 문제가 숫자 변환입니다. 엑셀은 숫자처럼 보이는 값을 자동으로 숫자로 바꾸기 때문에 다음 같은 일이 생깁니다.
01012345678 → 1012345678 (앞자리 0 사라짐)06236 → 62361.23457E+15)로 바뀌고 끝자리가 0으로 바뀜1-2 같은 값 → 날짜로 바뀜이것은 CSV의 문제가 아니라 CSV를 여는 프로그램의 동작입니다. CSV 파일 자체에는 01012345678이 그대로 들어 있어요. 해결하려면 가져오기 단계에서 해당 열을 ‘텍스트’ 형식으로 지정해야 합니다. 데이터를 넘기는 쪽이라면 하이픈을 넣은 형식(010-1234-5678)으로 내보내는 것도 방법입니다.
일부 유럽 언어 환경에서는 소수점으로 쉼표를 쓰기 때문에 CSV 구분자로 세미콜론(;)을 씁니다. 해외에서 받은 CSV가 한 열에 모든 값이 몰려 보인다면 구분자가 다른 경우입니다. 탭으로 구분한 TSV도 자주 쓰이는데, 값 안에 쉼표가 많은 데이터라면 TSV가 따옴표 처리 부담이 적어 편할 때가 있습니다.
CSV를 JSON으로 바꾸면 모든 값이 기본적으로 문자열이 됩니다. CSV에는 자료형 정보가 없기 때문이에요. 숫자로 바꿀 열과 문자열로 남길 열(전화번호, 우편번호)을 명시적으로 정해야 합니다. 빈 칸을 빈 문자열(“”)로 둘지 null로 둘지도 미리 정해 두면 나중에 데이터를 다룰 때 혼란이 적습니다. JSON 쪽 규칙은 JSON 문법 가이드에 정리했습니다.
modu.tools의 데이터 워크벤치는 CSV와 JSON을 브라우저 안에서 서로 변환합니다. 파일은 서버로 업로드되지 않습니다.
증상: 엑셀에서 연 CSV의 이름 열이 ``처럼 보이거나 Ã안처럼 이상한 서유럽 글자로 보입니다.
EC 95 88, CP949에서는 BE C8입니다. 뷰어나 hexdump로 앞부분을 보면 인코딩을 역산할 수 있습니다.같은 파일에서도 열마다 처리가 달라야 합니다.
| 열 | 권장 형식 | 이유 |
|---|---|---|
| 이름 | 일반/텍스트 | 한글 보존 |
| 전화번호 | 텍스트 | 앞자리 0 보존 |
| 금액 | 숫자 | 합계·정렬 |
| 주문번호 | 텍스트 | 긴 정수 정밀도 |
| 메모 | 텍스트 | 쉼표·줄바꿈 가능 |
RFC 4180 기준으로 값에 쉼표, 따옴표, 줄바꿈이 있으면 값을 큰따옴표로 감싸고, 값 안의 큰따옴표는 두 번("") 씁니다.
| 원본 값 | CSV 필드 |
|---|---|
서울 |
서울 또는 "서울" |
서울, 부산 |
"서울, 부산" |
그가 "완료"라고 함 |
"그가 ""완료""라고 함" |
| 두 줄 주소 | 따옴표로 감싼 채 줄바꿈 포함 |
내보내기 코드가 이 규칙을 지키지 않으면, 다음 단계 JSON 변환에서 열이 밀려 전혀 다른 키가 생깁니다.
""로 둘지 null로 둘지 정했는가?modu.tools의 CSV → JSON 변환기는 브라우저 안에서만 변환하므로, 샘플 데이터로 위 체크리스트를 먼저 검증하기 좋습니다.
헤더 줄은 꼭 있어야 하나요? RFC 4180에서는 선택 사항입니다. 하지만 데이터를 주고받을 때는 헤더를 넣는 것이 거의 항상 낫습니다.
줄바꿈이 LF만 있어도 되나요? RFC 4180은 CRLF를 규정하지만, 대부분의 프로그램은 LF만 있어도 문제없이 읽습니다.
BOM이 있는지 어떻게 확인하나요?
16진수 뷰어로 파일 앞 3바이트가 EF BB BF인지 보면 됩니다. 일부 코드 편집기는 상태 표시줄에 ’UTF-8 with BOM’으로 표시합니다.