-
같은 한글 파일명인데 검색이 안 맞을 때: NFC와 NFDProgramming 2026. 9. 17. 22:51728x90반응형

같은 모양을 서로 다른 구성으로 표현할 수 있음을 보여주는 AI 생성 개념 이미지입니다. 겉모양이 같은 한글 문자열이 다르게 비교되면 원문을 보존한 채 코드포인트와 NFC 정규화 결과를 확인하세요.
파일 목록에는 분명 같은 한글 이름이 있는데 프로그램의 일치 검색에서는 찾지 못하는 경우가 있습니다. 눈으로 보이는 글자 모양과 문자열에 들어 있는 코드포인트 배열이 다를 수 있기 때문입니다. 다만 한글 검색 문제를 전부 정규화 탓으로 돌릴 수는 없습니다. 경로, 공백, 인코딩, 검색 방식 등 다른 조건도 함께 확인해야 합니다.
이 글은 Python의 Unicode 처리 문서와 Unicode 정규화 표준을 바탕으로 원인을 좁히는 방법을 설명합니다. 기준일은 2026년 9월 13일입니다. 아래 코드는 문자열로 만든 예시이며 실제 폴더의 파일명을 읽거나 바꾸지 않습니다. 출력 설명은 지정한 입력에서 기대하는 관계이지 특정 OS의 파일시스템을 측정한 결과가 아닙니다.
한 글자처럼 보여도 내부 표현은 다를 수 있습니다
유니코드는 문자에 코드포인트라는 값을 부여합니다. 화면에 보이는 모양은 폰트와 렌더러가 그 값을 표현한 결과입니다. Python의 문자열은 코드포인트의 열로 다뤄지므로, 화면에서 한 글자처럼 보이는 요소가 항상 하나의 코드포인트인 것은 아닙니다. 문자·코드포인트·화면 모양의 구분
한글 ‘가’는
U+AC00한 개로 표현할 수 있습니다. 조합용 자모U+1100과U+1161의 열도 정규화 관점에서 같은 한글을 나타냅니다. 두 열은 정준적으로 동등하지만 원래 코드포인트 배열은 다릅니다. Unicode 표준도 한글 음절과 조합용 자모를 정준 동등성의 예로 설명합니다. 한글의 정준 동등성여기서 조합용 자모와 문서에 따로 적는 호환 자모를 구분해야 합니다. 눈에 ‘ㄱ’과 ‘ㅏ’처럼 보인다는 이유만으로 아무 자모 두 개나 이어 붙이면 같은 진단 입력이 되는 것은 아닙니다. 그래서 예시에서는 글자를 눈으로 다시 입력하지 않고 코드포인트를 명시합니다.
파일명이나 검색어를 메신저와 편집기에 여러 번 복사하면 중간 도구가 표현에 영향을 줄 가능성도 있습니다. 원인을 찾을 때는 가능하면 문제가 발생한 프로그램이 실제로 받은 문자열을 확인합니다. 눈에 보이는 이름만 새로 타이핑하면 다른 입력을 검사하게 될 수 있습니다.
NFC와 NFD는 같은 동등성의 다른 표현입니다
NFD는 정준 분해를 적용하는 형식이고, NFC는 정준 분해 뒤 가능한 정준 결합을 적용하는 형식입니다. Python에서는
unicodedata.normalize("NFC", text)처럼 호출해 정규화된 새 문자열을 얻습니다. 원래 문자열을 자동으로 덮어쓰거나 디스크의 파일명을 바꾸는 함수가 아닙니다. normalize()의 네 가지 형식이를 “NFD는 잘못된 한글, NFC는 올바른 한글”로 이해하면 곤란합니다. 서로 다른 정규화 표현을 어떤 비교 규칙으로 처리할지가 핵심입니다. 데이터가 NFD 형태로 들어왔다는 사실만으로 손상됐다고 판단하지 않습니다.
또 NFC가 모든 글자를 코드포인트 한 개로 만든다는 뜻도 아닙니다. 결합 가능한 조합에만 정해진 규칙을 적용합니다. 화면 글자 수를 세거나 커서를 이동하는 문제까지 NFC 변환 하나로 해결되는 것은 아닙니다. 정규화 형식의 정의
실무에서 NFC를 비교 기준으로 선택했다면 저장한 검색 키와 사용자가 입력한 검색어에 같은 규칙을 적용해야 합니다. 자료 쪽만 바꾸고 검색어는 원문 그대로 비교하면 문제의 절반만 다룬 셈이 됩니다.
먼저 원문 두 개를 나란히 확인합니다
아래 입력은 ‘가’를 두 방식으로 구성합니다.
\u표기는 Python 소스에서 코드포인트를 지정하는 방법입니다. 실제 파일명을 가져오지 않기 때문에 폴더에 어떤 자료가 있는지와 관계없이 예시의 비교 의도를 읽을 수 있습니다.import unicodedata as ud composed = "\uac00" decomposed = "\u1100\u1161" def inspect_text(text): return { "escaped": ascii(text), "codepoints": [f"U+{ord(c):04X}" for c in text], "names": [ud.name(c, "UNNAMED") for c in text], "length": len(text), } print(inspect_text(composed)) print(inspect_text(decomposed)) print("raw_equal", composed == decomposed) print( "nfc_equal", ud.normalize("NFC", composed) == ud.normalize("NFC", decomposed), ) print("unicode_database", ud.unidata_version)이 입력에서는 원문 비교가 서로 다르고 NFC를 적용한 비교는 같아지는 관계를 기대합니다. 첫 문자열은 코드포인트 한 개, 둘째는 두 개이기 때문입니다. 이는 파일 크기나 사용자가 보는 글자 수를 비교한 것이 아니라 Python 문자열의 코드포인트 배열을 확인하는 예시입니다.
ascii()와 코드포인트 목록을 함께 보는 이유는 화면에 같은 모양이 나와도 차이를 읽을 수 있게 하기 위해서입니다.unicodedata.name()은 문자의 이름을 확인하는 데 도움을 줍니다. 실제 진단 기록에는 실행한 Python과 Unicode 데이터베이스 버전도 남길 수 있습니다. 문자 이름과 데이터베이스 버전만약 실제 문제의 두 문자열이 NFC 이후에도 다르다면, 정준적으로 동등한 표현 차이만으로는 설명되지 않는다는 뜻입니다. 끝 공백, 다른 문자, 숨은 문자, 잘못 가져온 경로 등을 이어서 확인합니다. 정규화를 더 강하게 적용하기 전에 다른 코드포인트가 무엇인지부터 읽어야 합니다.
검색 키는 만들되 원래 이름도 남깁니다
가상의 파일 목록에 두 가지 표현의
가.txt와각.txt가 있다고 해 보겠습니다. 이것은 한 파일시스템에서 세 이름이 반드시 공존한다는 주장이 아닙니다. 여러 출처에서 받아 목록에 모은 문자열을 가정한 예시입니다.검색용 NFC 키를 만들 때 바로
키 → 파일 하나사전으로 덮어쓰면, 같은 키가 된 두 원본 중 하나를 잃을 수 있습니다. 먼저 키별로 원문을 모아 충돌을 확인합니다.import unicodedata as ud original_names = [ "\uac00.txt", "\u1100\u1161.txt", "\uac01.txt", ] by_search_key = {} for original in original_names: key = ud.normalize("NFC", original) by_search_key.setdefault(key, []).append(original) for key, originals in by_search_key.items(): if len(set(originals)) > 1: print("collision", ascii(key), [ascii(x) for x in originals])앞의 두 ‘가’ 이름은 같은 NFC 키에 모이므로 충돌 후보가 됩니다. 이 코드는 어느 쪽 파일을 지우거나 합칠지 결정하지 않습니다. 원문 이름과 출처 식별자를 유지한 채 사용자에게 후보를 보여 주거나, 이름 변경 전에 내용과 경로를 비교할 계기를 만드는 코드입니다.
검색 결과를 눌러 실제 파일을 열 때도 주의가 필요합니다. 검색 키로 바꾼 문자열을 곧바로 실제 경로라고 가정하지 말고, 일치한 항목에 연결된 원래 경로나 식별자를 사용합니다. 검색을 위한 동등성 규칙과 파일을 정확히 지정하는 규칙을 분리하는 것입니다.
NFKC를 쓰면 더 잘 고쳐지는 것 아닐까요
NFKC와 NFKD는 호환 동등성까지 다룹니다. 호환되는 표현을 통일하는 과정에서 원래 구별하던 기호나 표현이 같은 문자열이 될 수 있습니다. 예를 들어 로마 숫자 기호와 라틴 문자의 차이도 호환 정규화에서 영향을 받는 사례입니다. 정준 정규화와 호환 정규화의 차이
검색 편의를 위해 그런 차이를 같게 취급하려는 서비스도 있을 수 있습니다. 그러나 원문 보존, 식별자 일치, 문서 의미가 중요한 곳에 “더 강력한 정리”라는 이유로 일괄 적용할 수는 없습니다. 어떤 차이를 없애도 되는지 먼저 결정해야 합니다.
특히 비밀번호, 서명 대상 문자열, 외부 시스템이 발급한 식별자에 임의 정규화를 추가하면 상대가 기대한 원래 값과 달라질 수 있습니다. 적용할 프로토콜의 규칙이 정해져 있다면 그 규칙을 따라야 합니다. 파일명 검색 문제를 해결하려고 모든 텍스트 입력을 한꺼번에 정규화하는 변경은 범위가 너무 넓습니다.
정규화로 모든 닮은 문자를 구분할 수는 없습니다
시각적으로 비슷한 다른 문자와 정준적으로 같은 문자는 다른 문제입니다. Unicode는 혼동 가능한 문자에 대한 별도 보안 메커니즘을 다룹니다. NFC를 통과했다는 이유만으로 식별자 사칭이나 모든 동형 문자 문제를 막았다고 판단하면 안 됩니다. Unicode의 혼동 문자 처리
인코딩 오류도 구분해야 합니다. 바이트를 잘못 해석해 이미 대체 문자나 깨진 문자열이 생겼다면, NFC가 사라진 원래 바이트를 되살리는 것은 아닙니다. Python 문서는 문자열을 코드포인트로 다루는 일과 바이트를 인코딩·디코딩하는 일을 따로 설명합니다. 문자열과 바이트 변환
특정 운영체제 이름만 보고 항상 NFC 또는 항상 NFD라고 단정하지 않는 이유도 같습니다. 파일시스템, 압축·전송 도구, 앱의 문자열 처리 경로를 거친 실제 입력을 봐야 합니다. 이 글은 운영체제별 모든 파일명 처리 규칙을 검증한 자료가 아닙니다.
실제 이름을 바꾸기 전에 확인할 결과
진단은 원문 문자열과 코드포인트 확보, NFC 비교, 충돌 후보 확인 순서로 진행합니다. 검색 기능의 문제라면 우선 원문을 유지한 채 검색 키에만 같은 정규화 규칙을 적용하는 방식을 검토할 수 있습니다.
정말 파일명을 바꿔야 한다면 변경 전후 이름의 대응표, 이름 충돌, 기존 링크나 다른 프로그램의 참조를 먼저 확인합니다. 본문 예시는 변경 계획을 읽는 데까지이며 실제 rename 명령을 제공하지 않습니다. 원본을 보존한 시험 범위에서 결과를 확인한 뒤 적용할 문제입니다.
같은 모양의 한글이 검색에서 어긋날 때 NFC는 유용한 진단 수단입니다. 하지만 성공 기준은 변환 함수가 실행됐다는 것이 아닙니다. 원래 자료를 잃지 않고 올바른 항목을 찾으며, 여러 원본이 한 키로 모일 때도 조용히 덮어쓰지 않는 상태까지 확인해야 합니다.
728x90반응형'Programming' 카테고리의 다른 글
Python 프로젝트가 여러 개일 때 uv workspace로 묶어도 될까 (1) 2026.09.17 파이썬 파일 하나만 보내도 실행되게 만들기: uv 스크립트 의존성 (0) 2026.09.17 브라우저에 저장한 메모가 사라질 수 있을까 (0) 2026.09.16 Bitwarden 백업 파일, 새 계정에서도 복원할 수 있을까 (0) 2026.09.16 iCloud 용량은 남는데 아이폰이 꽉 찼다면 (0) 2026.09.16