본문 바로가기
텍스트·문서·13분 소요

한글 자모 분리(ㅎㅏㄴㄱㅡㄹ) 원인과 해결법: 유니코드 NFC·NFD 완전정리

맥에서 만든 파일 이름이 윈도우에서 자음·모음으로 흩어지는 건 인코딩 깨짐이 아니라 유니코드 정규화(NFD) 문제다. NFC·NFD·NFKC 차이와 검색·글자수·파일명이 망가지는 이유, 정규화해도 안 합쳐지는 호환 자모 함정까지 코드포인트 단위로 정리했다.

한글 자모 분리(ㅎㅏㄴㄱㅡㄹ) 원인과 해결법: 유니코드 NFC·NFD 완전정리

광고

작성 툴박스 운영자최초 작성 최종 검토

툴박스 가이드는 W3C·MDN·국제 표준 문서(RFC/ISO)와 한국인터넷진흥원(KISA) 등 공신력 있는 기관의 공개 자료를 기반으로 작성·검토합니다.편집 방침 보기

맥에서 압축해 보낸 제주도 여행 사진.zip을 윈도우에서 풀면 폴더 이름이 ㅈㅔㅈㅜㄷㅗ ㅇㅕㅎㅐㅇ ㅅㅏㅈㅣㄴ처럼 자음과 모음으로 흩어진다. 겉보기엔 글자가 망가진 것 같지만 한 글자도 손상되지 않았다. 제주도 여행이라는 글자는 그대로 있고, 다만 저장 형태가 6개 코드포인트(16바이트)가 아니라 12개 코드포인트(34바이트)일 뿐이다. 이 글은 그 차이가 어디서 생기는지, 검색·글자 수·파일명 길이를 어떻게 조용히 망가뜨리는지, 그리고 정규화를 돌렸는데도 안 합쳐지는 경우의 진짜 원인까지 코드포인트 단위로 정리한다.

자모 분리는 '인코딩 깨짐'과 다른 문제다

한글이 이상하게 보이는 현상은 크게 두 갈래인데, 원인도 해결법도 정반대다. 둘을 섞어 놓고 검색하면 엉뚱한 해법만 나온다.

구분자모 분리 (정규화 문제)인코딩 깨짐 (모지바케)
화면 예시ㅎㅏㄴㄱㅡㄹ���, ÇѱÛ, 한±Û
원인같은 글자를 자모로 풀어서 저장(NFD)저장 문자셋과 해석 문자셋 불일치(EUC-KR ↔ UTF-8)
원문 보존100% 보존 — 조합만 하면 원상복구변환 경로에 따라 복구 불가능한 경우가 있다
글자 수한 글자가 2~3자로 늘어난다대체로 그대로거나 물음표로 치환
해결유니코드 정규화(NFC)올바른 문자셋으로 다시 읽기

자모 분리는 데이터가 깨진 게 아니라 같은 글자의 다른 표기법이다. 반면 인코딩이 깨진 쪽은 이미 바이트가 손실됐을 수 있어 복구가 훨씬 까다롭다. 인코딩 깨짐 쪽이라면 한글 인코딩 깨짐 복구 가이드를 먼저 보는 게 빠르다.

'한' 한 글자를 코드포인트로 뜯어보기

유니코드에는 한글이 두 벌 들어 있다. 하나는 조합이 끝난 완성형 음절이고, 다른 하나는 조립용 부품인 자모다.

  • 한글 음절 영역: U+AC00(가) ~ U+D7A3(힣), 총 11,172자
  • 한글 자모 영역: 초성 U+1100~U+1112(19자), 중성 U+1161~U+1175(21자), 종성 U+11A8~U+11C2(27자)

음절 코드포인트는 다음 식으로 계산된다.

음절 = 0xAC00 + (초성번호 × 21 + 중성번호) × 28 + 종성번호
'한' = 0xAC00 + (18 × 21 + 0) × 28 + 4 = 0xD55C

19 × 21 × 28 = 11,172라는 음절 수가 여기서 나온다. 같은 '한'을 두 방식으로 적으면 이렇게 갈린다.

글자NFC(조합형)NFD(분해형)NFC 길이NFD 길이
U+AC00U+1100 U+11611자 / 3바이트2자 / 6바이트
U+D55CU+1112 U+1161 U+11AB1자 / 3바이트3자 / 9바이트
U+AE00U+1100 U+1173 U+11AF1자 / 3바이트3자 / 9바이트
사진U+C0AC U+C9C4자모 5개2자 / 6바이트5자 / 15바이트

받침이 없으면 2배, 받침이 있으면 정확히 3배로 늘어난다. 제주도 여행(공백 포함 6자)이 NFC 16바이트에서 NFD 34바이트가 되는 이유다.

왜 맥에서 온 파일에서만 보이나

macOS의 옛 파일 시스템 HFS+는 파일 이름을 저장할 때 애플 변형 NFD로 강제 정규화했다. macOS 10.13부터 기본이 된 APFS는 정규화를 강제하지 않고 입력된 형태를 그대로 보존하지만, 그 이전에 만들어진 파일명과 기존 도구·클라우드 동기화 클라이언트가 만들어 둔 NFD 이름이 계속 전파된다.

환경파일명 정규화화면에서 티가 나나
macOS HFS+NFD로 강제 변환안 남 — 렌더링이 자모를 합쳐 그린다
macOS APFS정규화 안 함(보존)안 남
Windows NTFS정규화 안 함 — 자모가 그대로 노출
Linux ext4정규화 안 함
웹 브라우저 입력대부분 NFC

핵심은 macOS의 텍스트 렌더링이 흩어진 자모를 합자로 그려 준다는 점이다. 그래서 맥 사용자는 자기 파일이 NFD인 걸 끝까지 모른 채 압축해서 보내고, 받는 쪽 윈도우에서만 문제가 드러난다. 여행 사진처럼 폴더 이름에 지명·날짜가 잔뜩 들어간 묶음일수록 눈에 잘 띈다. 다녀온 행사 이름으로 폴더를 나누는 편이라면 전국 축제 일정에서 공식 행사명을 확인해 표기를 통일해 두면, 나중에 폴더 검색이 훨씬 수월해진다.

정규화 형식 4종 비교 — 어디에 무엇을 쓰나

유니코드 정규화는 UAX #15(Unicode Normalization Forms)가 정의한 네 가지 형식이 전부다. 이름은 조합(C)/분해(D), 정준(canonical)/호환(K)의 조합이다.

형식방향'한'㈜ U+321C㎏ U+338FA U+FF21권장 용도
NFC정준 조합U+D55C (1자)㈜ 유지㎏ 유지A 유지저장·전송 기본값
NFD정준 분해자모 3자㈜ 유지㎏ 유지A 유지macOS 내부 표현
NFKC호환 조합U+D55C(주)kgA검색 색인·중복 판정
NFKD호환 분해자모 3자(주)kgA형태소 분석 전처리

W3C의 웹 문자 모델 권고(Character Model for the World Wide Web)는 웹 콘텐츠의 저장·전송 기본값으로 NFC를 제시한다. 특별한 이유가 없으면 이 하나만 쓰면 된다.

NFKC는 원문 저장에 쓰면 안 된다. 1이 되고 ½1⁄2가 되며 합자가 fi 두 글자로 풀린다. 의미가 바뀌는 변환이라, 검색 키를 따로 만들 때만 쓰고 본문은 NFC로 보존하는 게 원칙이다. 이런 특수 기호를 다룰 일이 잦다면 특수문자 모음에서 어떤 글자가 호환 문자인지 미리 확인해 두면 사고를 줄일 수 있다.

자모 분리가 조용히 부수는 것 6가지

문제는 "보기 싫다"에서 끝나지 않는다. 아래는 전부 실제로 재현되는 결과다.

깨지는 것NFCNFD결과
문자열 비교"한글""한글"불일치 — 눈으로는 같은데 ==가 false
정규식 [가-힣]+매칭됨매칭 안 됨한글 검증 로직이 전부 통과 실패
글자 수9자18자원고지·SNS 글자 제한에서 사고
파일명 한계(255바이트)한글 85자받침 있으면 28자긴 이름이 잘려 저장 실패
URL 인코딩 '한'%ED%95%9C (9자)%E1%84%92%E1%85%A1%E1%86%AB (27자)링크 길이 3배, 같은 페이지가 다른 URL로
정렬 순서U+AC00대U+1100대NFD 파일만 목록 맨 앞으로 튄다

가장 자주 사고가 나는 건 문자열 비교다. 엑셀 VLOOKUP이 분명히 있는 값을 못 찾고, DB WHERE name = '홍길동'이 0건을 돌려주고, 브라우저 Ctrl+F가 화면에 보이는 단어를 못 찾는다. 셋 다 원인은 같다.

두 번째는 글자 수다. 제주도 여행 사진 2026년 여름 가족은 공백 포함 21자인데, NFD 상태에서는 38자로 센다. 글자 수 제한이 걸린 폼에서 "왜 다 안 들어가지"가 여기서 나온다. 의심되면 글자수 세기 도구에 붙여넣어 눈으로 센 개수와 비교해 보면 된다. 2~3배 차이가 나면 거의 확실히 NFD다.

상황별 해결법

파일명 (맥 → 윈도우)

이미 받은 파일이라면 이름을 일괄로 바꾸는 게 가장 확실하다. 파이썬이 깔린 환경이면 아래 스크립트로 폴더를 통째로 정리할 수 있다.

import os, unicodedata as u

for root, dirs, files in os.walk('.', topdown=False):
    for name in dirs + files:
        fixed = u.normalize('NFC', name)
        if fixed != name:
            os.rename(os.path.join(root, name), os.path.join(root, fixed))
            print(f'{name} -> {fixed}')

리눅스·macOS에서는 convmv -f utf-8 -t utf-8 --nfc -r --notest . 한 줄로도 된다. 일부 압축 프로그램은 해제 옵션에 자모 결합(유니코드 정규화) 항목을 제공하므로, 압축을 푸는 시점에 미리 켜 두면 이 작업 자체가 필요 없다.

텍스트 본문

문서 안의 본문이 분리돼 있다면 텍스트를 NFC로 다시 쓰는 수밖에 없다. 엑셀에는 정규화 함수가 아예 없으므로 셀 안에서 해결하려 하지 말고, 텍스트 변환 도구나 편집기를 한 번 거쳐 붙여넣는 편이 빠르다.

개발 코드

환경코드
JavaScript (ES2015+)s.normalize('NFC')
Pythonunicodedata.normalize('NFC', s)
JavaNormalizer.normalize(s, Normalizer.Form.NFC)
PostgreSQL 13+normalize(col, NFC), 검사는 col IS NFC NORMALIZED
Git (macOS)git config core.precomposeunicode true

Git 설정은 특히 놓치기 쉽다. 이 값이 꺼져 있으면 맥에서 추가한 한글 파일명이 NFD 그대로 커밋되고, 리눅스 CI에서 "그런 파일 없음"으로 빌드가 죽는다. 화면에는 분명 파일이 보이는데 못 찾는다며 한참을 헤매게 되는 유형이다.

이미 섞여 들어간 데이터는 한 번에 정리한다.

-- PostgreSQL 13 이상
UPDATE articles
   SET title = normalize(title, NFC)
 WHERE NOT title IS NFC NORMALIZED;

원칙은 하나다. 입력 경계에서 딱 한 번 정규화한다. 파일 업로드 핸들러, 폼 제출, API 요청 본문에서 NFC로 맞춰 놓으면 그 뒤 로직은 아무것도 신경 쓸 필요가 없다. 반대로 조회할 때마다 정규화하는 방식은 인덱스를 못 타서 성능만 버린다.

정규화했는데도 안 합쳐진다면 — 호환 자모 함정

가장 많이 막히는 지점이다. 'ㅎㅏㄴ'.normalize('NFC')를 돌리면 결과가 ㅎㅏㄴ 그대로다. 정규화가 고장 난 게 아니라, 애초에 다른 글자이기 때문이다.

보이는 글자정식 명칭코드포인트NFC로 합쳐지나
ᄒ ᅡ ᆫ한글 자모 (조합용)U+1112 U+1161 U+11AB합쳐진다
ㅎ ㅏ ㄴ한글 호환 자모U+314E U+314F U+3134안 합쳐진다

키보드에서 한글 자음만 눌러 나오는 (U+3131)은 호환 자모로, NFD 분해로 생기는 (U+1100)과는 코드포인트가 아예 다르다. 호환 자모는 그 자체로 완결된 독립 문자라 정준 조합 대상이 아니다.

그럼 NFKC를 쓰면 되지 않을까? 여기서 더 나쁜 일이 생긴다. 'ㅎㅏㄴ'을 NFKC로 돌리면 하ᄂ이 된다. 호환 자모 (U+3134)이 초성 니은(U+1102)으로 매핑되는데, 초성은 앞 음절의 받침이 될 수 없어서 다음에 이 홀로 남는 것이다. 무심코 NFKC를 돌렸다가 데이터를 더 망가뜨리는 경로다.

판별은 코드포인트로 한다. **U+1100~U+11FF면 조합용 자모(정규화로 해결), U+3130~U+318F면 호환 자모(별도 매핑 필요)**다. 후자는 호환 자모 → 조합용 자모 매핑 테이블을 만들어 초성·중성·종성 위치를 직접 판정한 뒤 조합해야 한다. 화면에서 눈으로 구분하려면 자모 사이 간격을 보면 된다. 붙어서 한 글자처럼 보이면 조합용, 각각 한 칸씩 또렷하게 떨어져 있으면 호환 자모다.

팀 단위로 막는 체크리스트

개인 PC에서 한 번 고치는 것으로는 끝나지 않는다. 아래 순서대로 우선순위를 두면 재발이 잡힌다.

  1. 저장 경계 NFC 강제 (최우선) — 업로드 파일명, 폼 입력, API 요청 본문. 여기 한 곳만 막아도 신규 유입은 0이 된다.
  2. 기존 데이터 일괄 정규화 — 위 SQL로 한 번 훑고, 정규화 이후에 유니크 제약이 충돌하지 않는지 먼저 확인한다.
  3. 검색 색인은 NFKC로 별도 생성 — 본문 원본은 NFC로 두고 색인만 호환 정규화해서 (주)가 같이 잡히게 한다.
  4. macOS 개발자 Git 설정 확인git config --get core.precomposeunicode 결과가 비어 있으면 즉시 true로.
  5. CI에 파일명 검사 추가 — 저장소에 NFD 파일명이 들어오면 빌드를 실패시킨다.
  6. 외부 공유 파일명은 영문·숫자 권장 — 근본 해결은 아니지만 사고 빈도를 크게 줄인다.

자주 묻는 질문

맥에서 보낸 파일 이름이 윈도우에서만 자음 모음으로 보이는데, 파일 내용도 손상된 건가요? 아니다. 파일 이름의 표현 방식만 다를 뿐 내용은 전혀 건드려지지 않는다. 사진은 그대로 열리고 문서도 정상이다. 이름을 NFC로 바꾸면 화면 표시도 원래대로 돌아온다.

정규화하면 원래 글자가 바뀌지는 않나요? NFC와 NFD 사이의 변환은 정준 등가(canonical equivalence)라 의미 손실이 전혀 없다. 몇 번을 왕복해도 같은 글자다. 다만 NFKC·NFKD는 1로, kg로 바꾸는 호환 변환이라 원문 저장용으로 쓰면 안 된다.

게시글 수천 건이 이미 자모 분리 상태로 DB에 들어갔습니다. 어떻게 정리하나요? PostgreSQL 13 이상이면 UPDATE ... SET col = normalize(col, NFC) WHERE NOT col IS NFC NORMALIZED로 대상 행만 골라 한 번에 처리된다. 실행 전에 반드시 백업하고, 해당 컬럼에 유니크 인덱스가 걸려 있다면 정규화 후 중복이 생기지 않는지 먼저 조회해 확인한다.

글자 수가 이상하게 세지는데 자모 분리인지 어떻게 확인하나요? 텍스트를 글자수 도구에 붙여넣고 눈으로 센 글자 수와 비교하면 즉시 알 수 있다. 2배 안팎이면 받침 없는 글자 위주의 NFD, 3배에 가까우면 받침 있는 글자가 많은 NFD다. 정상 NFC라면 눈으로 센 수와 정확히 일치한다.

아이폰이나 아이패드에서 만든 파일도 같은 문제가 생기나요? 같은 계열의 파일 시스템을 쓰기 때문에 클라우드 동기화나 압축 과정에서 NFD 파일명이 만들어질 수 있다. 다만 최신 기기에서 새로 만든 파일은 정규화를 강제하지 않으므로, 문제가 생기는 쪽은 대부분 오래전에 만들어져 계속 옮겨 다닌 파일들이다.

웹 서비스를 만들 때 어디까지 신경 써야 하나요? 서버가 받는 모든 사용자 입력을 NFC로 정규화하고, 파일 업로드 시 원본 파일명을 그대로 저장하지 않는 두 가지면 대부분 막힌다. 개발 도구 전반이 궁금하다면 kimgoon 라이프스타일 가이드에 인코딩·정규식·JSON 관련 글이 정리돼 있다.

관련 가이드

참고한 표준·공식 자료

본 글은 다음 기관·표준 문서의 공개 자료를 바탕으로 정리·검토되었습니다. 최신 기준은 각 원문을 함께 확인해 주세요.

잘못된 정보나 갱신이 필요한 부분을 발견하셨다면 contact@kimgoon.kr로 알려주세요. 툴박스 편집 방침을 함께 참고하실 수 있습니다.

공유

광고

다른 서비스의 관련 가이드