본문 바로가기

파일 형식 백과사전

ZIP 압축을 풀었더니 파일명이 ║╕░φ╝¡ 처럼 깨질 때, 원인과 해결

받은 압축 파일을 풀었더니 안에 든 한글 파일명이 ║╕░φ╝¡.txt 같은 외계어나 물음표(???)로 바뀌어 있는 경우가 있습니다. 정작 파일을 열면 내용은 멀쩡한데 이름만 알아볼 수 없습니다.

파일이 망가진 것이 아닙니다. ZIP이 파일명을 저장하는 방식과, 압축을 푸는 프로그램이 그 이름을 읽는 방식이 어긋났을 뿐입니다. 윈도우 11과 파이썬 3.14.3 표준 라이브러리 zipfile로 직접 만들어 확인한 결과를 근거로 풀어 보겠습니다.

 

ZIP은 파일명을 어떻게 저장하나

ZIP 안에는 각 파일의 이름이 바이트로 적혀 있고, "이 이름이 어떤 인코딩이냐"를 알려 주는 작은 표식이 따로 있습니다. ZIP 규격에서 이 표식은 일반 목적 플래그의 11번째 비트, 값으로는 0x800입니다.

이 비트가 켜져 있으면 "파일명은 UTF-8"이라는 뜻입니다. 꺼져 있으면 인코딩이 정해져 있지 않아, 압축 푸는 쪽이 자기 시스템 기본 인코딩으로 짐작해서 읽습니다. 한국어 윈도우에서는 그 짐작이 보통 CP949입니다.

문제는 오래된 한국어 압축 프로그램들이 이 비트를 켜지 않고, 파일명을 CP949 바이트 그대로 넣어 둔다는 점입니다. 그 ZIP을 UTF-8로 읽는 환경에서 풀면 바이트 해석이 어긋나 이름이 깨집니다.


실제로 만들어 보니 이렇게 갈립니다

같은 파일명 보고서.txt를 두 가지 ZIP으로 만들었습니다. 하나는 UTF-8 플래그를 끄고 이름을 CP949 바이트로 넣은 레거시 방식, 다른 하나는 UTF-8 플래그를 켠 현대 방식입니다. 그리고 둘 다 파이썬 zipfile로 다시 읽었습니다.

먼저 디스크에 실제로 적힌 한글 파일명의 바이트입니다.

보고서.txt (CP949) : ba b8 b0 ed bc ad 2e 74 78 74   (10 byte)

이 같은 바이트를 두 ZIP이 어떤 플래그로 감싸느냐에 따라 읽히는 결과가 달라집니다.

ZIP UTF-8 비트(0x800) 읽은 파일명 결과
레거시(CP949) 꺼짐 ║╕░φ╝¡.txt 깨짐
현대(UTF-8) 켜짐 보고서.txt 정상

레거시 ZIP에서 zipfile이 내놓은 이름이 바로 ║╕░φ╝¡.txt입니다. 이름이 깨진 것은 파일 손상이 아니라, 인코딩 표식이 없어 읽는 쪽이 다른 규칙으로 해석한 결과입니다.

왜 압축 프로그램마다 결과가 다를까

같은 ZIP을 윈도우 기본 압축 풀기, 7-Zip, 반디집류로 각각 풀면 결과가 갈리는 일이 흔합니다. 원인은 단순합니다. UTF-8 비트가 꺼진 ZIP을 만났을 때 이름을 무슨 인코딩으로 짐작하느냐가 프로그램마다 다르기 때문입니다.

시스템 기본 코드페이지(CP949)로 짐작하는 도구는 한국어 윈도우에서 이름을 맞게 풉니다. 반대로 무조건 UTF-8로 읽거나 다른 언어 코드페이지로 짐작하는 도구는 같은 파일에서 이름을 깨뜨립니다. 파일이 달라진 게 아니라 짐작이 달라진 것입니다.


해결책 1: 보내는 쪽이 UTF-8로 압축하기

가장 깔끔한 해결은 압축을 만드는 단계에서 UTF-8 플래그를 켜는 것입니다. 받는 사람이 어떤 OS를 쓰든 이름이 그대로 보입니다. 최신 7-Zip, 반디집, 맥과 리눅스의 기본 압축은 대체로 UTF-8로 파일명을 저장합니다.

파이썬 zipfile은 파일명에 한글처럼 비ASCII 문자가 있으면 UTF-8 플래그를 자동으로 켜 줍니다. 위 현대 ZIP이 그렇게 만들어졌습니다.

import zipfile

with zipfile.ZipFile("modern.zip", "w") as z:
    z.write("보고서.txt")   # 비ASCII 이름이면 zipfile이 0x800 플래그를 자동으로 켠다

다만 받는 사람이 전부 구버전 한국어 윈도우 압축기만 쓴다면, 그쪽에서는 UTF-8 이름이 도리어 깨져 보일 수 있습니다. 환경에 따라 다를 수 있으니 받는 환경을 알면 거기에 맞추는 편이 안전합니다.

해결책 2: 받는 쪽이 인코딩을 지정해 풀기

이미 받은 ZIP이 레거시 방식이라 고칠 수 없다면, 푸는 쪽에서 이름을 바로잡을 수 있습니다. 핵심은 zipfile이 플래그 없는 이름을 임시로 CP437로 디코딩해 둔다는 점입니다. 그래서 CP437로 되돌린 뒤 CP949로 다시 읽으면 원래 이름이 복원됩니다.

import zipfile, os

with zipfile.ZipFile("legacy.zip") as z:
    for info in z.infolist():
        name = info.filename
        if not (info.flag_bits & 0x800):          # UTF-8 플래그가 없으면
            name = name.encode("cp437").decode("cp949")  # CP949로 재해석
        with open(name, "wb") as f:
            f.write(z.read(info))

실제로 위 코드를 레거시 ZIP에 돌리니 깨졌던 ║╕░φ╝¡.txt보고서.txt로 복원되어 저장되었습니다. 플래그가 꺼진 한글 ZIP은 cp437로 인코딩한 뒤 cp949로 디코딩하면 이름이 돌아옵니다.

GUI 도구만 쓴다면, 7-Zip의 압축 풀기 옵션이나 반디집의 인코딩 선택 메뉴에서 파일명 인코딩을 한국어(CP949)로 바꿔 주면 같은 효과를 얻습니다. 메뉴 위치와 표기는 버전에 따라 다를 수 있습니다.

자주 묻는 질문

Q. 이름이 깨진 채로 풀렸는데 그 파일을 다시 압축하면 굳어 버리나요?
이름은 다시 바꿀 수 있습니다. 다만 한 번 잘못 디코딩된 이름은 원본 정보를 일부 잃을 수 있으니, 가능하면 원본 ZIP을 위 방법으로 다시 푸는 편이 확실합니다.

Q. 맥에서 만든 ZIP을 윈도우에서 풀 때도 깨지던데요.
방향만 반대인 같은 문제입니다. 맥 기본 압축은 UTF-8로 저장하는데, 그 ZIP을 UTF-8 비트를 무시하고 CP949로 짐작하는 구형 도구로 풀면 깨집니다. UTF-8을 제대로 읽는 도구로 풀면 해결됩니다.

Q. 내용물(텍스트)도 같이 깨졌어요.
그건 파일명이 아니라 파일 안 글자의 인코딩 문제로, 별개의 원인입니다. 압축과 무관하게 그 파일 자체를 올바른 인코딩으로 다시 열어야 합니다.

 

한글 파일명이 깨지면 ZIP의 UTF-8 비트(0x800)가 켜졌는지부터 떠올려 보세요. 보내는 쪽은 UTF-8 지원 도구로 묶고, 받는 쪽은 인코딩을 CP949로 지정해 풀면 양쪽 어디서도 이름이 살아남습니다.

 

 

 

같은 사진을 WebP, AVIF, JPG로 저장하면 용량이 얼마나 차이 날까 (직접 측정 후기)

웹에 사진을 올릴 때 형식 하나만 바꿔도 용량이 크게 줄어든다는 말을 자주 듣습니다. WebP가 좋다, 이제는 AVIF다, 하는 이야기 속에서 정작 "내 사진은 얼마나 줄어드는가"는 손에 잡히지 않습니

richyeon.com

 

 

같은 폴더를 ZIP, 7Z, TAR.GZ로 압축하면 용량이 얼마나 다를까, 3종 직접 측정

파일 여러 개를 하나로 묶어 보낼 때 ZIP으로 할지 7Z로 할지, 아니면 개발자들이 자주 쓰는 TAR.GZ로 할지 고민한 적이 있을 것입니다. 다들 "7Z가 더 작다"고 하는데, 정말 그런지, 차이가 얼마나 나

richyeon.com

 

 

XML로 받은 데이터를 JSON으로 바꾸고 싶을 때, 표준 라이브러리만으로 해결하기

API나 설정 파일을 XML로 받았는데, 쓰려는 라이브러리나 프런트엔드는 JSON만 받는 상황을 만나신 적이 있을 것입니다. 둘 다 데이터를 담는 형식인데 모양이 달라서, 어떻게 옮겨야 깔끔한지 막

richyeon.com