macOS와 iOS의 한글이름 파일을 윈도우로 가져왔을 때, 자음과 모음이 분리된 한글을 경로를 지정하여 일괄 변경하는 파이썬 코드 v2.1
문제 상황과 해결 방안 소개
맥북이나 아이폰에서 작업한 파일들을 윈도우 PC로 옮기다 보면 한글 파일명이 알아볼 수 없게 깨져버리는 경험을 해보셨을 것입니다. ‘보고서_최종.pdf’가 ‘ㅂㅗㄱㅗㅅㅓ_ㅊㅗㅟㄱㅏㅇ.pdf’처럼 변해버리는 현상이죠. 이런 현상은 단순히 보기 좋지 않을 뿐만 아니라, 파일을 찾고 관리하는 데 큰 어려움을 줍니다.
이러한 문제가 발생하는 이유는 각 운영체제가 한글을 저장하는 방식이 다르기 때문입니다. 이 글에서는 이 문제를 해결하는 파이썬 프로그램을 만들어보겠습니다. 경로만 지정하면 폴더 내의 모든 파일명을 자동으로 수정해주는 편리한 도구를 함께 만들어보시죠.
한글 파일명이 깨지는 기술적 원인
운영체제마다 한글을 저장하는 방식이 다르다는 것은 무슨 의미일까요? 간단한 예시로 설명해보겠습니다.
한글 ‘가’를 컴퓨터에 저장할 때 두 가지 방식이 있습니다:
1. 완성형 방식: ‘가’를 하나의 문자로 저장
2. 조합형 방식: ‘ㄱ’과 ‘ㅏ’를 따로 저장
이것을 수학적으로 표현하면 다음과 같습니다:
$$
\text{macOS/iOS 방식} = \text{‘ㄱ’} + \text{‘ㅏ’} \text{ (NFD)}
$$
$$
\text{Windows 방식} = \text{‘가’} \text{ (NFC)}
$$
이 차이 때문에 운영체제 간에 파일을 옮길 때 문제가 발생하는 것입니다.
Python을 활용한 해결 방법
이제 본격적으로 문제 해결을 위한 Python 프로그램을 살펴보겠습니다. 프로그램은 크게 세 가지 핵심 기능을 수행합니다: 파일 검색, 파일명 분석, 그리고 정규화입니다. 이 과정을 순서도로 표현하면 다음과 같습니다:
flowchart TB
Start[시작] --> Select[폴더 선택]
Select --> Scan[폴더 검사]
Scan --> Check{파일명 확인}
Check -- 정규화 필요 --> Convert[파일명 변환]
Check -- 정규화 불필요 --> Next[다음 파일]
Convert --> Next
Next --> |파일 남음| Scan
Next --> |완료| End[종료]
코드 상세 설명과 사용 방법
먼저 전체 코드를 보고, 각 부분이 어떤 역할을 하는지 자세히 설명하겠습니다. Python을 처음 접하시는 분들을 위해 각 줄마다 상세한 설명을 덧붙였습니다.
# 필요한 라이브러리를 가져옵니다
import tkinter as tk # 그래픽 인터페이스를 만들기 위한 도구
from tkinter import filedialog, messagebox # 파일 선택 창과 메시지 창을 위한 도구
import os # 파일과 폴더를 다루기 위한 도구
import unicodedata # 한글 변환을 위한 도구
class FileNameNormalizer(tk.Tk):
def __init__(self):
super().__init__() # 기본 창 초기화
# 프로그램 창의 제목과 크기 설정
self.title("한글 파일명 정규화 프로그램")
self.geometry("600x400")
# 메인 화면 구성
main_frame = tk.Frame(self, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 파일 경로 입력 부분 구성
path_frame = tk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=(0, 10))
# 경로 표시를 위한 텍스트 상자
self.path_var = tk.StringVar()
path_entry = tk.Entry(path_frame, textvariable=self.path_var)
path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
# 폴더 선택 버튼
browse_btn = tk.Button(path_frame, text="폴더 선택", command=self.browse_directory)
browse_btn.pack(side=tk.RIGHT)
이 코드의 첫 부분은 프로그램의 기본 뼈대를 구성합니다. 마치 집을 지을 때 기초 공사를 하는 것과 같습니다. 각 부분이 하는 일을 자세히 살펴보겠습니다:
1. 필요한 도구 준비:
– tkinter: 프로그램의 창과 버튼 등을 만드는 도구
– os: 파일과 폴더를 다루는 도구
– unicodedata: 한글 변환을 처리하는 도구
2. 프로그램의 기본 구조 설정:
– 600×400 크기의 창 생성
– 폴더 선택 버튼과 경로 표시 창 배치
– 파일 목록을 보여줄 공간 마련
이제 프로그램의 핵심 기능을 수행하는 부분의 코드를 살펴보겠습니다. 이 부분은 실제로 파일명을 검사하고 변환하는 중요한 역할을 합니다.
def scan_directory_recursive(self, directory):
"""재귀적으로 디렉토리를 스캔하여 정규화가 필요한 파일들을 찾습니다."""
try:
# 현재 디렉토리 내의 모든 항목을 검사합니다
for entry in os.scandir(directory):
try:
if entry.is_file(): # 파일인 경우
if self.needs_normalization(entry.name):
# 현재 경로에서의 상대 경로를 구합니다
relative_path = os.path.relpath(entry.path, self.path_var.get())
self.result_list.insert(tk.END, relative_path)
self.total_files += 1
elif entry.is_dir(): # 폴더인 경우
# 시스템 폴더는 건너뜁니다
if self.is_system_or_hidden(entry.path):
continue
# 폴더명도 정규화 대상에 포함
if self.needs_normalization(entry.name):
relative_path = os.path.relpath(entry.path, self.path_var.get())
self.result_list.insert(tk.END, relative_path + '/')
self.total_files += 1
# 하위 디렉토리도 검사합니다
self.scan_directory_recursive(entry.path)
except PermissionError:
continue # 접근 권한 오류는 무시하고 진행
except Exception as e:
continue # 기타 오류도 안전하게 처리
except PermissionError:
return # 디렉토리 접근 권한 오류 처리
except Exception:
return # 예상치 못한 오류 처리
이 코드는 마치 도서관에서 책을 정리하는 사서처럼 작동합니다. 각 폴더(책장)를 하나씩 살펴보면서, 정리가 필요한 파일(책)을 찾아내는 것이죠. 주요 기능을 하나씩 살펴보겠습니다:
1. 폴더 검사 (scan_directory_recursive):
– 지정된 폴더 안의 모든 파일과 하위 폴더를 검사합니다
– 시스템 폴더나 숨김 폴더는 건너뜁니다
– 오류가 발생해도 안전하게 처리합니다
2. 파일명 정규화 필요성 검사:
def needs_normalization(self, filename):
"""파일명이 정규화가 필요한지 검사합니다"""
nfc = unicodedata.normalize('NFC', filename) # Windows 방식으로 변환
return filename != nfc # 변환 전과 후가 다르면 정규화 필요
이 함수는 마치 의사가 진찰하듯이 각 파일명을 검사합니다. 정규화가 필요한 파일명을 찾아내는 것이죠.
3. 실제 파일명 변환 처리:
def normalize_filenames(self):
"""선택된 모든 파일의 이름을 정규화합니다"""
directory = self.path_var.get()
if not directory: # 디렉토리가 선택되지 않은 경우
messagebox.showwarning("경고", "디렉토리를 선택해주세요.")
return
try:
self.normalized_files = 0 # 처리된 파일 수 초기화
self.normalize_directory_recursive(directory) # 실제 변환 작업 수행
# 작업 완료 후 결과 표시
self.result_list.delete(0, tk.END)
messagebox.showinfo("완료",
f"총 {self.normalized_files}개의 파일/폴더명이 정규화되었습니다.")
# 카운터 초기화
self.total_files = 0
self.normalized_files = 0
except Exception as e:
messagebox.showerror("오류", f"파일명 변환 중 오류 발생: {str(e)}")
실전 활용과 주의사항
이제 프로그램을 실제로 어떻게 사용하는지, 그리고 사용할 때 어떤 점들을 주의해야 하는지 살펴보겠습니다. 특히 처음 프로그램을 사용하시는 분들을 위해 단계별로 자세히 설명해드리겠습니다.
기본적인 사용 방법
프로그램을 실행하면 가장 먼저 해야 할 일은 처리할 파일들이 있는 폴더를 선택하는 것입니다. ‘폴더 선택’ 버튼을 클릭하면 윈도우의 폴더 선택 창이 나타납니다. 여기서 처리하고 싶은 폴더를 선택하면 됩니다.
폴더를 선택하면 프로그램은 자동으로 그 폴더 안에 있는 모든 파일과 하위 폴더를 검사합니다. 정규화가 필요한 파일들의 목록이 프로그램 창에 표시됩니다. 이 목록을 확인하신 후, ‘파일명 정규화’ 버튼을 클릭하면 실제 변환 작업이 시작됩니다.
안전한 사용을 위한 주의사항
파일명을 변경하는 작업은 신중하게 진행해야 합니다. 다음과 같은 점들을 특히 주의해주세요:
1. 작업 전 백업
변환 작업을 시작하기 전에 반드시 중요한 파일들은 백업해두세요. 예기치 못한 상황에 대비하는 것이 좋습니다. 다음과 같은 방식으로 백업할 수 있습니다:
# 백업 기능 예시 코드
def backup_directory(source_path):
"""중요 파일 백업을 위한 함수"""
backup_path = source_path + "_backup"
import shutil
shutil.copytree(source_path, backup_path)
2. 시스템 폴더 주의
프로그램은 자동으로 윈도우의 중요한 시스템 폴더들을 건드리지 않도록 설계되어 있습니다. 하지만 혹시 모를 상황에 대비해, 다음과 같은 폴더들은 처리하지 않는 것이 좋습니다:
– Windows 설치 폴더
– Program Files 폴더
– 시스템 복구 관련 폴더
3. 네트워크 드라이브 사용 시 주의사항
네트워크 드라이브나 외장 하드의 파일을 처리할 때는 연결이 안정적인지 확인하세요. 불안정한 네트워크 상태에서는 작업을 피하는 것이 좋습니다.
효율적인 사용을 위한 팁
프로그램을 더욱 효과적으로 활용하기 위한 몇 가지 팁을 소개합니다:
1. 처리 순서 최적화
대용량 폴더를 처리할 때는 다음과 같은 순서로 진행하는 것이 좋습니다:
– 먼저 소규모 폴더로 테스트
– 정상 작동 확인 후 대규모 폴더 처리
– 처리 중에는 다른 프로그램 사용 자제
2. 정기적인 관리
정기적으로 파일명을 검사하고 정규화하면 문제를 예방할 수 있습니다. 특히 다음과 같은 경우에 정기 점검을 추천합니다:
– 맥OS 사용자와 파일을 주고받은 후
– 클라우드 스토리지에서 파일을 다운로드한 후
– 외부 저장장치에서 파일을 복사한 후
정리 및 추가 팁
지금까지 한글 파일명 정규화 프로그램의 구현과 사용 방법에 대해 자세히 살펴보았습니다. 이 프로그램은 단순히 파일명을 수정하는 도구를 넘어, 여러 운영체제를 오가며 작업해야 하는 현대 업무 환경에서 매우 유용한 도구가 될 수 있습니다.
프로그램의 장점과 한계
이 프로그램의 가장 큰 장점은 사용자가 수동으로 일일이 파일명을 수정할 필요 없이, 폴더를 선택하는 것만으로 모든 작업이 자동으로 처리된다는 점입니다. 특히 수백, 수천 개의 파일을 다루어야 하는 경우 매우 효율적입니다.
다만, 몇 가지 제한사항도 있습니다:
1. 실행 중인 프로그램의 파일은 수정할 수 없습니다.
2. 네트워크 상태가 불안정한 경우 원격 파일 처리가 어려울 수 있습니다.
3. 매우 긴 경로의 파일(260자 이상)은 윈도우 시스템 제한으로 인해 처리가 어려울 수 있습니다.
활용 가능한 응용 분야
이 프로그램의 코드는 다양한 방식으로 확장하여 활용할 수 있습니다. 예를 들어:
1. 파일 이름 일괄 변환 기능 추가
def batch_rename(self, pattern, replacement):
"""파일 이름을 일괄적으로 변환하는 기능"""
for file in self.file_list:
new_name = file.replace(pattern, replacement)
if self.needs_normalization(new_name):
new_name = unicodedata.normalize('NFC', new_name)
os.rename(file, new_name)
2. 자동 모니터링 기능
def watch_directory(self, path):
"""특정 폴더를 주기적으로 모니터링"""
import time
while True:
self.normalize_filenames()
time.sleep(3600) # 1시간마다 체크
운영체제 간의 파일명 호환성 문제는 앞으로도 계속 존재할 것입니다. 이는 각 운영체제가 가진 고유한 특성 때문입니다. 하지만 이 프로그램을 통해 이러한 문제를 쉽고 효율적으로 해결할 수 있습니다.
korean-filename-normalizer_v2.1.exe 다운로드
전체 소스 코드는 다음과 같습니다:
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import unicodedata
class FileNameNormalizer(tk.Tk):
def __init__(self):
super().__init__()
self.title("한글 파일명 정규화 프로그램")
self.geometry("600x400")
# 메인 프레임
main_frame = tk.Frame(self, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 경로 입력 프레임
path_frame = tk.Frame(main_frame)
path_frame.pack(fill=tk.X, pady=(0, 10))
self.path_var = tk.StringVar()
path_entry = tk.Entry(path_frame, textvariable=self.path_var)
path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
browse_btn = tk.Button(path_frame, text="폴더 선택", command=self.browse_directory)
browse_btn.pack(side=tk.RIGHT)
# 결과 리스트박스
self.result_list = tk.Listbox(main_frame)
self.result_list.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 변환 버튼
convert_btn = tk.Button(main_frame, text="파일명 정규화", command=self.normalize_filenames)
convert_btn.pack()
# 진행 상황을 위한 변수
self.total_files = 0
self.normalized_files = 0
def browse_directory(self):
directory = filedialog.askdirectory()
if directory:
self.path_var.set(directory)
self.scan_directory(directory)
def scan_directory(self, directory):
self.result_list.delete(0, tk.END)
try:
self.scan_directory_recursive(directory)
except Exception as e:
messagebox.showerror("오류", f"디렉토리 스캔 중 오류 발생: {str(e)}")
def is_system_or_hidden(self, path):
"""시스템 폴더나 숨김 폴더인지 확인합니다."""
# 무시할 시스템 폴더 목록
system_folders = ['$Recycle.Bin', 'System Volume Information', 'Recovery',
'Config.Msi', '$WinREAgent', 'Documents and Settings']
folder_name = os.path.basename(path)
# 시스템 폴더 체크
if folder_name.upper() in (name.upper() for name in system_folders):
return True
# 숨김 속성 체크
try:
import stat
return bool(os.stat(path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN)
except:
return False
def scan_directory_recursive(self, directory):
"""재귀적으로 디렉토리를 스캔하여 정규화가 필요한 파일들을 찾습니다."""
try:
for entry in os.scandir(directory):
try:
if entry.is_file():
if self.needs_normalization(entry.name):
relative_path = os.path.relpath(entry.path, self.path_var.get())
self.result_list.insert(tk.END, relative_path)
self.total_files += 1
elif entry.is_dir():
# 시스템 폴더나 숨김 폴더는 건너뜀
if self.is_system_or_hidden(entry.path):
continue
# 폴더명도 정규화 대상에 포함
if self.needs_normalization(entry.name):
relative_path = os.path.relpath(entry.path, self.path_var.get())
self.result_list.insert(tk.END, relative_path + '/')
self.total_files += 1
self.scan_directory_recursive(entry.path)
except PermissionError:
continue # 접근 권한 오류 무시
except Exception as e:
continue # 기타 오류 무시
except PermissionError:
return # 디렉토리 접근 권한 오류 무시
except Exception:
return # 기타 오류 무시
def needs_normalization(self, filename):
nfc = unicodedata.normalize('NFC', filename)
return filename != nfc
def normalize_filenames(self):
directory = self.path_var.get()
if not directory:
messagebox.showwarning("경고", "디렉토리를 선택해주세요.")
return
try:
self.normalized_files = 0
self.normalize_directory_recursive(directory)
self.result_list.delete(0, tk.END)
messagebox.showinfo("완료",
f"총 {self.normalized_files}개의 파일/폴더명이 정규화되었습니다.")
# 카운터 초기화
self.total_files = 0
self.normalized_files = 0
except Exception as e:
messagebox.showerror("오류", f"파일명 변환 중 오류 발생: {str(e)}")
def normalize_directory_recursive(self, directory):
"""재귀적으로 디렉토리를 순회하며 파일명을 정규화합니다."""
try:
# 먼저 파일들을 정규화
for entry in os.scandir(directory):
try:
if entry.is_file():
if self.needs_normalization(entry.name):
old_path = entry.path
new_filename = unicodedata.normalize('NFC', entry.name)
new_path = os.path.join(os.path.dirname(old_path), new_filename)
os.rename(old_path, new_path)
self.normalized_files += 1
except PermissionError:
continue
except Exception:
continue
# 그 다음 하위 디렉토리들을 처리
dirs_to_normalize = []
for entry in os.scandir(directory):
try:
if entry.is_dir():
# 시스템 폴더나 숨김 폴더는 건너뜀
if self.is_system_or_hidden(entry.path):
continue
# 먼저 하위 디렉토리의 내용을 정규화
self.normalize_directory_recursive(entry.path)
# 디렉토리 자체의 이름이 정규화가 필요한 경우 목록에 추가
if self.needs_normalization(entry.name):
dirs_to_normalize.append(entry)
except PermissionError:
continue
except Exception:
continue
# 마지막으로 디렉토리 이름들을 정규화
for dir_entry in dirs_to_normalize:
try:
old_path = dir_entry.path
new_dirname = unicodedata.normalize('NFC', dir_entry.name)
new_path = os.path.join(os.path.dirname(old_path), new_dirname)
os.rename(old_path, new_path)
self.normalized_files += 1
except PermissionError:
continue
except Exception:
continue
except PermissionError:
return
except Exception:
return
if __name__ == "__main__":
app = FileNameNormalizer()
app.mainloop()
이 코드를 기반으로 여러분의 필요에 맞게 수정하고 발전시켜 사용하시기 바랍니다. 더 나은 파일 관리를 통해 업무 효율성을 높이는 데 도움이 되길 바랍니다.