paper&honey

직접 실험·검증한 가이드

작성 paper&honey 운영자 게시 수정

맥 자소분리 — 한글 파일명 깨짐, 압축 3종 바이트 검증

거래처에서 "파일명이 이상하게 왔다"는 연락을 받고 확인해 보면, 맥에서 압축해 보낸 "월간보고"가 윈도우 쪽 목록에는 "ㅇㅝㄹㄱㅏㄴㅂㅗㄱㅗ"처럼 자음·모음이 낱낱이 흩어진 채로 떠 있습니다. 이것이 그 유명한 맥 자소분리 현상인데, 겉보기엔 파일이 깨진 것 같아서 당황하게 됩니다.

먼저 안심하셔도 됩니다 — 파일 내용은 멀쩡합니다. 깨진 것은 파일명의 표기 방식뿐이고, 원인은 macOS와 윈도우가 한글을 저장하는 유니코드 방식(NFD vs NFC)이 다르기 때문입니다. 그리고 매일 쓰는 압축 도구만 바꾸면 보내는 쪽에서 완전히 예방됩니다. 이 가이드는 "그렇다더라"로 끝내지 않습니다. 실제 맥(macOS 26.5.1)에서 같은 한글 파일을 세 가지 방법으로 압축한 뒤 zip 내부 파일명을 파이썬으로 바이트 단위까지 검사했고, 그 결과를 가공 없이 실었습니다. 결과가 다소 의외였습니다.

맥 자소분리는 왜 생기나 — NFD와 NFC

한 줄로 답하면, 맥은 한글 파일명을 자모 단위로 풀어서(NFD) 저장하는 경우가 많고 윈도우는 완성형(NFC)을 기대하기 때문입니다. 유니코드에서 "한"이라는 글자는 두 가지 방식으로 표현할 수 있습니다. 완성형 코드 하나로 적는 방식이 NFC(완성형·Composed), ㅎ+ㅏ+ㄴ 세 개의 자모 코드를 이어 붙이는 방식이 NFD(분리형·Decomposed)입니다. 둘은 화면에 똑같이 "한"으로 보이는 것이 정상인데, 윈도우 쪽 프로그램 상당수가 NFD를 조합해서 그려주지 않고 자모를 낱글자 그대로 나열해 버립니다. 그래서 "월간보고"가 "ㅇㅝㄹㄱㅏㄴㅂㅗㄱㅗ"로 보이는 것입니다.

중요한 것은 이것이 이름 표기의 문제일 뿐이라는 점입니다. 압축이 손상된 것도, 문서 내용이 날아간 것도 아닙니다. 파일을 열면 내용은 그대로 있습니다. 그러니 맥 자소분리 화면을 봤다고 파일을 다시 받을 필요는 없습니다 — 이름만 고치면 됩니다. 자소 분리와 인코딩 깨짐(외계어)이라는 두 증상의 구분은 zip 파일 한글 깨짐 가이드에서 원리와 인코딩(CP949) 실측으로 다뤘고, 이 글은 그중 자소 분리를 실측으로 파고듭니다.

NFD는 어디서 생기나 — Finder와 터미널이 다르다

결과부터 보면, 같은 맥, 같은 디스크인데 파일명을 만든 주체에 따라 NFD가 되기도 NFC가 되기도 합니다. 직접 확인해 보고 저희도 좀 놀란 부분입니다. 흔히 "맥 파일시스템은 무조건 NFD"라고 알려져 있는데, 그것은 옛날 HFS+ 시절 이야기입니다. 지금의 APFS는 정규화를 강제하지 않고 앱이 준 이름을 그대로 저장합니다. 그래서 실험을 해봤습니다. Finder에서 "최종보고서"라는 폴더를 만들고, 터미널에서 "보고서_최종.txt"라는 파일을 만든 뒤, 파이썬으로 디스크에 실제로 저장된 이름을 검사했습니다.

import os, unicodedata
for n in os.listdir('.'):
    form = 'NFC' if n == unicodedata.normalize('NFC', n) else \
           ('NFD' if n == unicodedata.normalize('NFD', n) else '혼합')
    print(f'{form}  {n!r}  ({len(n)}자)')
NFC  '보고서_최종.txt'  (10자)   ← 터미널(echo)로 만든 파일
NFD  '최종보고서'  (11자)        ← Finder로 만든 폴더

Finder가 만든 "최종보고서"는 화면엔 5글자인데 디스크엔 11개 코드포인트로 저장돼 있습니다 — "최종"만 봐도 ㅊ+ㅚ+ㅈ+ㅗ+ㅇ 식으로 자모가 풀린 NFD입니다. 반면 터미널에서 만든 파일은 10자(한글 5 + 언더바 1 + 확장자 4), 즉 완성형 NFC 그대로입니다. Finder가 파일명을 NFD로 만들어내는 것이 맥 자소분리의 출발점이고, 우리가 파일을 만들고 이름 짓는 곳은 대부분 Finder입니다.

압축하면 어떻게 되나 — zip·ditto 실측

결론은 이렇습니다 — 맥 기본 압축은 NFD 파일명을 그대로 zip 안에 담습니다. 심지어 문제가 하나 더 있습니다. NFD 파일명("월간보고_최종.txt", 디스크에 20자모)을 두 가지 기본 도구로 압축해 봤습니다. zip 명령과, Finder "압축하기"와 같은 형태의 zip을 만드는 ditto입니다. 그리고 zip 포맷 명세대로 내부 파일명 레코드를 검사하는 스크립트를 짰습니다.

import zipfile, unicodedata, glob

for zpath in sorted(glob.glob("*.zip")):
    with zipfile.ZipFile(zpath) as z:
        for info in z.infolist():
            utf8 = bool(info.flag_bits & 0x800)   # zip의 UTF-8 파일명 플래그
            raw = info.filename.encode("utf-8") if utf8 \
                  else info.filename.encode("cp437")
            name = raw.decode("utf-8")
            if name.startswith("._"):             # 맥 리소스포크 항목 제외
                continue
            form = "NFC(완성형)" if name == unicodedata.normalize("NFC", name) \
                   else "NFD(자소분리)"
            print(f"{zpath}: {form}, 유니코드 {len(name)}자, UTF-8플래그 {utf8}")
nfd-zip-cli.zip: NFD(자소분리), 유니코드 20자, UTF-8플래그 False
nfd-ditto.zip:   NFD(자소분리), 유니코드 20자, UTF-8플래그 False

두 가지가 확인됩니다. 첫째, NFD가 zip 내부까지 그대로 전파됩니다 — 압축 과정에서 아무도 이름을 완성형으로 바꿔주지 않습니다. 둘째, UTF-8 플래그(zip 헤더의 비트 11)조차 설정하지 않습니다. 이 플래그가 없으면 윈도우 압축 프로그램은 파일명을 UTF-8이 아니라 시스템 레거시 인코딩으로 해석하려 들 수 있어서, 자소분리를 넘어 아예 외계어로 깨질 여지까지 생깁니다. 즉 맥 기본 압축은 윈도우 호환 관점에서 이중으로 불리합니다.

Keka로 압축하면? — 실측 결과가 의외였다

답부터 말씀드리면 — Keka는 압축하는 시점에 파일명을 NFC 완성형으로 바꾸고, UTF-8 플래그까지 설정합니다. 받는 윈도우에서 깨질 요소를 둘 다 없앱니다. 검색해 보면 "Keka 쓰면 한글 잘 된다"는 후기는 많은데, 정작 Keka가 파일명을 어떻게 처리하는지 확인한 데이터는 찾기 어렵습니다. 그래서 위와 같은 NFD 파일을 Keka(1.6.7)에 드래그해서 zip으로 압축하고, 같은 스크립트로 검사했습니다.

# Keka로 압축한 zip을 같은 스크립트로 검사한 결과
월간보고_최종.txt.zip: NFC(완성형), 유니코드 11자, UTF-8플래그 True

원본 파일명은 디스크에서 20자모짜리 NFD였는데, Keka가 만든 zip 안에는 11자 완성형(NFC)으로 들어가 있습니다. UTF-8 플래그도 켜져 있습니다. 압축 단계에서 정규화를 해준다는 뜻이라, 받는 쪽이 어떤 윈도우 압축 프로그램을 쓰든 자소분리를 볼 일이 없습니다. 솔직히 "덜 깨지는 정도"를 기대했는데, 바이트를 까보니 정석대로 처리하고 있었습니다. 같은 NFD 파일인데, 압축 방법에 따라 결과가 이렇게 갈립니다.

압축 방법 zip 내부 파일명 UTF-8 플래그 윈도우에서
zip 명령 NFD 그대로 자소분리 (+인코딩 깨짐 여지)
ditto (Finder 압축과 같은 형태) NFD 그대로 동일
Keka 1.6.7 NFC로 변환 정상 표시

반디집과 반디네이머는 어떤가

반디집 macOS 버전도 공식 문서에서 "반디집 macOS는 파일명을 기본적으로 NFC로 처리하므로, 맥 반디집으로 압축해 윈도우에서 풀면 파일명이 올바르게 표시된다(Since Bandizip for macOS handles a filename using NFC by default…)"고 설명하고 있습니다. 방향은 Keka 실측 결과와 같습니다. 다만 맥 앱스토어의 반디집이 유료로 전환돼 있어서, 이 글의 바이트 검사는 직접 해보지 못했습니다 — 위 표에 반디집이 빠진 이유입니다. 문서 기준으로는 믿고 쓸 수 있는 선택지로 보입니다.

같은 회사의 반디네이머(BandiNamer)는 무료인데 역할이 다릅니다. 압축 도구가 아니라 디스크의 파일명 자체를 NFD↔NFC로 변환해 주는 도구입니다. 압축 없이 USB나 클라우드로 파일을 넘길 때는 압축 도구가 개입할 틈이 없으니, 이런 경우 보내기 전에 반디네이머로 이름을 완성형으로 바꿔두는 방식이 유효합니다.

이미 깨진 채 받았다면

보내는 쪽을 못 바꾸는 상황, 즉 자소분리된 파일명을 이미 받아버린 윈도우 사용자라면 파일명을 일괄 교정해 주는 도구를 쓰면 됩니다. 윈도우용 반디집 최신 버전은 압축 해제 시 자소분리 파일명을 처리해 주고, "한글 자소 교정기" 같은 무료 유틸도 있습니다. 설치 없이 해결하고 싶다면 zip을 풀기 전에 paper&honey의 zip 한글 깨짐 복구 도구에 올려서 정상 파일명 zip으로 다시 받는 방법도 있습니다. 파일이 서버로 올라가지 않고 브라우저 안에서만 처리됩니다. 파일 개수가 적으면 그냥 이름을 다시 타이핑해도 됩니다 — 어차피 내용은 멀쩡하니까요.

개발자라면 파이썬 몇 줄로 끝납니다. 폴더 안 파일명을 전부 NFC로 바꾸는 코드입니다.

import os, unicodedata
for n in os.listdir('.'):
    nfc = unicodedata.normalize('NFC', n)
    if n != nfc:
        os.rename(n, nfc)

참고로 이 반대 방향 사고도 있습니다. 윈도우에서 만든 zip이 맥에서 아예 안 풀리는 문제인데, 그것은 자소분리가 아니라 압축 알고리즘(deflate64) 문제라 원인이 전혀 다릅니다. 그 과정은 맥 압축 오류 79 가이드에 따로 정리해 뒀습니다.

깨지는 범위를 정리하면

Q. 파일 내용도 깨진 건가요? 아닙니다. 맥 자소분리는 파일명 표기 문제일 뿐이고 데이터는 손상되지 않습니다. 이름이 깨진 상태로도 파일은 정상적으로 열립니다.

Q. 클라우드(드롭박스·구글드라이브)로 보내면 괜찮나요? 일부 클라우드 서비스는 동기화 과정에서 파일명을 완성형으로 바꿔준다고 알려져 있지만, 서비스와 시점에 따라 동작이 다를 수 있어 일반화하기 어렵습니다. 확실한 것은 보내는 쪽에서 이름을 NFC로 만들어 보내는 것 — 압축이라면 Keka류, 압축 없이 보낸다면 반디네이머로 변환 후 전송입니다.

Q. 맥끼리 주고받을 때도 문제가 되나요? 표시 문제는 없습니다. 맥은 NFD를 조합해서 정상적으로 그려줍니다. 다만 개발자라면 조심할 것이 하나 있는데, 문자열 비교에서 NFC "한글"과 NFD "한글"은 다른 문자열입니다. 서버에 업로드한 파일명 매칭이 안 되거나 스크립트에서 파일을 못 찾는 버그가 이 지점에서 나옵니다. 화면에 똑같이 보여서 원인을 찾기가 유난히 고약합니다.

결론 — 뭘 바꾸면 되나

맥 자소분리 예방은 도구 하나 바꾸는 것으로 끝납니다. 맥에서 윈도우로 한글 파일을 보낼 일이 있다면, 압축을 Finder 우클릭 대신 Keka로 하면 됩니다. 실측으로 확인했듯 Keka는 압축 시점에 파일명을 NFC로 정규화하고 UTF-8 플래그까지 챙깁니다. 반디집(유료)도 문서상 같은 방식이고, 압축 없이 보낼 때는 반디네이머로 이름을 먼저 변환하면 됩니다. 이미 깨진 zip을 받았다면 zip 한글 깨짐 복구 도구로 브라우저에서 바로잡을 수 있습니다.

그리고 이런 종류의 문제를 만났을 때 "어느 도구가 좋다더라"에서 멈추지 말고 위 파이썬 스크립트처럼 직접 까보시길 권합니다. zip 안에 뭐가 들었는지 확인하는 데 열 줄이면 충분하고, 한 번 확인해 두면 다음부터는 도구를 의심하느라 시간을 쓸 일이 없습니다. 기기 사이를 오가는 파일 문제는 이름 말고 포맷에서도 터집니다. 아이폰 HEIC 사진이 회사 PC에서 안 열리는 문제도 결국 같은 교훈입니다 — 증상만 보고 파일이 깨졌다고 단정하지 말 것.