macOS는 한글 파일명을 자모 단위로 쪼개서(NFD, 분해형) 저장합니다. 반면 윈도우·리눅스·대부분의 웹은 글자를 합쳐서(NFC, 조합형) 다룹니다. 화면에선 똑같이 안녕.txt 로 보이지만 내부 바이트가 다릅니다.
문제는 업로드할 때 드러납니다. 브라우저(특히 Chrome)는 파일명을 디스크에 저장된 형태 그대로 서버에 보냅니다 — 따로 정규화하지 않습니다. 그래서 NFD로 저장된 맥 파일은 자모가 흩어진 채 서버에 도착합니다.
맥에서 만든 파일 → 디스크엔 NFD → 브라우저가 NFD 그대로 전송 → 서버에서 ㅈㅏㅁㅗ 분리
| 내 Mac에서는 | 윈도우 · 웹 업로드에서는 |
|---|---|
보고서.hwp ✅ |
ㅂㅗㄱㅗㅅㅓ.hwp ❌ |
안녕 사진들/ ✅ |
ㅇㅏㄴㄴㅕㅇ ㅅㅏㅈㅣㄴㄷㅡㄹ/ ❌ |
윈도우에서 올릴 땐 멀쩡한 이유 — 윈도우가 만든 파일명은 처음부터 NFC라서입니다. 그래서 Dropbox·iCloud 같은 동기화 폴더엔 맥에서 만든 NFD 파일이 계속 섞여 쌓입니다. 한 번 정리해도 새로 유입될 수 있으니, 가끔 한 번씩 폴더째 정리해 주는 게 좋습니다.
nfd2nfc 는 파일·폴더 이름을 NFC로 바꿔 이 문제를 없앱니다. 보이는 글자는 그대로 두고 내부 인코딩만 정규화하므로 안전합니다.
세 가지 방법 중 하나만 고르면 됩니다.
brew install wonjun-lab/tap/nfd2nfcnfd2nfc 터미널 명령이 설치됩니다.
- Releases 에서
nfd2nfc-quick-action.zip을 내려받아 압축을 풉니다. - 나온
NFC로 이름 정리.workflow를 더블클릭 → “빠른 동작을 설치하시겠습니까?” 에서 설치. - 끝! 이제 파일·폴더를 우클릭 → 빠른 동작 → NFC로 이름 정리.
더블클릭이 보안으로 막히면 파일을 우클릭 → 열기 로 한 번만 실행하세요.
git clone https://github.com/wonjun-lab/nfd2nfc.git
cd nfd2nfc
./install.shFinder 우클릭 메뉴와 nfd2nfc 명령이 함께 설치됩니다. 제거는 ./uninstall.sh.
Finder에서 — 파일이나 폴더(여러 개 동시 선택도 가능)에서 우클릭 → 빠른 동작 → NFC로 이름 정리. 폴더를 고르면 그 안쪽까지 한 번에 정리하고, 끝나면 알림이 뜹니다.
터미널에서 —
nfd2nfc ~/Downloads/내폴더 # 폴더 안 전체 정리 (하위 포함)
nfd2nfc --dry-run ~/Desktop/*.hwp # 바꾸기 전에 미리보기
nfd2nfc --notify ~/사진들 # 끝나면 알림 표시
nfd2nfc -v 보고서.pdf 자료.xlsx # 여러 파일 + 변경 내역 출력| 옵션 | 설명 |
|---|---|
-n, --dry-run |
실제로 바꾸지 않고 무엇이 바뀔지 미리보기 |
--no-recurse |
지정한 항목만 처리 (하위 폴더 안 들어감) |
--notify |
완료 후 macOS 알림 |
--reveal |
완료 후 Finder에서 결과 보여주기 |
-q, --quiet |
조용히 실행 (요약 출력 생략, 경고·에러만) |
-f, --force |
이름 충돌 시 덮어쓰기 (기본은 건너뜀) |
--skip |
이름 충돌 시 건너뛰기 (기본) |
-v, --verbose |
변경 내역을 한 줄씩 출력 |
-V, --version |
버전 출력 |
-h, --help |
도움말 |
자주 NFD 파일이 들어오는 폴더(다운로드·Dropbox 등)를 등록해 두면, 새 파일이 생길 때마다 백그라운드에서 자동으로 NFC로 정리합니다. macOS 기본 launchd만 쓰며(의존성 0), 변경이 있을 때만 조용히 알립니다.
nfd2nfc watch add ~/Downloads ~/Dropbox # 등록 + 즉시 1회 정리
nfd2nfc watch list # 등록 폴더·상태 보기
nfd2nfc watch off # 잠시 중지
nfd2nfc watch on # 재개
nfd2nfc watch remove ~/Dropbox # 해제- 등록 폴더는 하위까지 정리합니다. 무한루프 없이(idempotent + 10초 간격) 안전하게 동작합니다.
- 로그:
~/Library/Logs/nfd2nfc-watch.log· 설정:~/Library/Application Support/nfd2nfc/ - 켜고 끄기는 로그인 세션 단위로 유지됩니다(launchd LaunchAgent).
네. 보수적으로, 되돌릴 일이 없게 동작합니다.
- 화면에 보이는 글자는 그대로 — 내부 유니코드 정규형만 바꿉니다.
- 이미 정상(NFC)인 파일은 손대지 않습니다.
- 여러 번 실행해도 안전합니다(idempotent). 바꿀 게 없으면 아무 일도 일어나지 않습니다.
- 깊은 폴더부터 처리해, 폴더 이름을 바꿔도 하위 경로가 어긋나지 않습니다.
- 심볼릭 링크를 따라 들어가지 않습니다.
- 권한이 없어 못 읽거나 못 바꾸는 항목은 건너뛰고 알립니다(종료 코드 1).
- Dropbox·iCloud 등 동기화 폴더에서도 안전 — 이름만 바꾸므로(메타데이터만 변경), 클라우드 전용(아직 안 받은) 파일을 통째로 내려받지 않습니다.
💡 왜 단순 비교가 아니라 inode를 보냐면 — macOS 파일시스템(APFS·HFS+)은 정규형을 구분하지 않아, NFD 파일을 NFC 이름으로 조회해도 “이미 있다”고 나옵니다. 그래서 nfd2nfc는 단순 존재 검사 대신 inode를 비교해, 정말로 다른 파일이 그 이름을 차지한 경우에만 건너뜁니다.
nfd2nfc는 파일·폴더 이름만 NFC로 바꿉니다. 그래서:
- 파일 내용은 건드리지 않습니다. 확장자·형식(
png·jpg·pdf·hwp·docx·xlsx등)과 무관하게, 이름만 정규화하고 내용·형식은 그대로 둡니다. - 압축 파일 안의 이름은 바꾸지 않습니다.
zip·tar, 그리고 내부가 압축인hwpx·docx같은 파일은 파일 자체 이름만 정규화됩니다. 압축 안에 든 한글 파일명이 NFD라면 다른 OS에서 풀 때 여전히 깨집니다 — 내부까지 고치려면 macOS에서 풀어 정규화한 뒤 다시 압축하세요. - 앱 번들(
.app)은 피하세요. macOS는.app을 폴더로 다뤄, 통째로 정리하면 내부까지 들어갑니다. 보통은 무해하지만 코드서명된 앱은 서명이 무효화될 수 있습니다. --force는 거의 쓸 일이 없습니다. macOS 기본 볼륨(APFS·HFS+)에선 NFD와 NFC가 같은 파일이라 이름 충돌이 생기지 않습니다.--force는 정규형을 구분하는 일부 외장·네트워크 볼륨에서만 의미가 있고, 그곳에선 같은 이름의 다른 파일을 영구히 덮어쓰므로(복구 불가) 주의하세요. 기본값(충돌 시 건너뜀)을 권장합니다.
받는 서버를 직접 운영한다면, 업로드 시점에 서버에서 정규화하는 것이 가장 완전합니다.
import unicodedata
filename = unicodedata.normalize("NFC", filename)nfd2nfc는 그게 불가능한, 올리는 쪽 사용자를 위한 처방입니다.
Quick Action을 직접 만들기 (수동 폴백)
install.sh 나 zip을 쓸 수 없을 때, Automator로 직접 만들 수 있습니다.
Automator → 새 문서 → 빠른 동작 → 받는 입력: 파일 또는 폴더, 위치: Finder.app →
셸 스크립트 실행 추가 → 셸: /bin/zsh, 입력 전달: 인수로 → 아래를 붙여넣고
이름을 NFC로 이름 정리 로 저장합니다.
/usr/bin/perl -e 'use strict; use warnings;
use Unicode::Normalize qw(NFC);
use Encode qw(decode_utf8 encode_utf8);
my @t;
sub col {
my $p = shift; $p =~ s{/+$}{}; return if $p eq "";
push @t, $p;
if (-d $p && !-l $p && opendir(my $d, $p)) {
my @e = readdir($d); closedir($d);
for my $x (@e) { next if $x eq "." || $x eq ".."; col("$p/$x"); }
}
}
col($_) for @ARGV;
my ($c, $s) = (0, 0);
for my $p (sort { ($b =~ tr{/}{}) <=> ($a =~ tr{/}{}) } @t) {
my $i = rindex($p, "/");
my $dir = $i == -1 ? "" : substr($p, 0, $i + 1);
my $base = $i == -1 ? $p : substr($p, $i + 1);
my $u = eval { my $cp = $base; decode_utf8($cp, Encode::FB_CROAK) };
next unless defined $u;
my $nb = encode_utf8(NFC($u));
next if $nb eq $base;
my $new = $dir . $nb;
# APFS는 정규화 비구분 → NFC 이름도 자기 자신으로 잡힌다.
# inode를 비교해 "진짜 다른 파일"이 있을 때만 건너뛴다.
my @cur = lstat($p); my @tgt = lstat($new);
if (@tgt && (!@cur || $tgt[0] != $cur[0] || $tgt[1] != $cur[1])) { $s++; next; }
$c++ if rename($p, $new);
}
my $msg = "이름 정리 완료: ${c}개 변경" . ($s ? ", ${s}개 건너뜀" : "");
system("/usr/bin/osascript", "-e",
"display notification \"$msg\" with title \"NFC 이름 정리\"");' "$@"배포되는
nfd2nfc-quick-action.zip은build-workflow.sh가nfd2nfc본문을 그대로 임베드해 자동 생성합니다. 스크립트를 고치면./build-workflow.sh로 다시 만드세요.
테스트·릴리스 절차는 CONTRIBUTING.md 를, 변경 이력은 CHANGELOG.md 를 참고하세요.
./test.sh # 통합 테스트 (macOS 전용)nfd2nfc fixes macOS NFD filenames — Korean names like 안녕.txt that appear as
ㅇㅏㄴㄴㅕㅇ.txt on Windows or web uploads — by normalizing files and folders to NFC.
Zero dependencies; it uses the perl that already ships with macOS. The visible
characters stay the same; only the underlying Unicode form is normalized.
| Install | How |
|---|---|
brew install wonjun-lab/tap/nfd2nfc |
|
Download nfd2nfc-quick-action.zip from Releases, unzip, double-click NFC로 이름 정리.workflow |
|
git clone … && cd nfd2nfc && ./install.sh |
nfd2nfc [--dry-run] [--no-recurse] [--notify] [--reveal] [-q] [-f] [-v] [-V] [-h] <paths…>
Safe by design: already-NFC files are left untouched, clashes with genuinely different files are skipped, and re-running is idempotent.
MIT License · macOS 한글 파일명 NFD→NFC 정리