Zum Hauptinhalt springen

파일 μžλ™ν™”

Python을 μ‚¬μš©ν•˜λ©΄ 반볡적인 파일 μž‘μ—…μ„ μžλ™ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 파일과 폴더λ₯Ό 생성, μ‚­μ œ, 이동, λ³΅μ‚¬ν•˜λŠ” μž‘μ—…μ„ μ½”λ“œλ‘œ μ²˜λ¦¬ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

핡심 λͺ¨λ“ˆβ€‹

os λͺ¨λ“ˆβ€‹

μš΄μ˜μ²΄μ œμ™€ μƒν˜Έμž‘μš©ν•˜λŠ” κΈ°λ³Έ λͺ¨λ“ˆμž…λ‹ˆλ‹€.

import os

# ν˜„μž¬ μž‘μ—… 디렉토리 확인
current_dir = os.getcwd()
print(f"ν˜„μž¬ 디렉토리: {current_dir}")

# 디렉토리 λ³€κ²½
os.chdir('/Users/username/Documents')

# 디렉토리 λͺ©λ‘ 보기
files = os.listdir('.')
print(files)

# 경둜 κ²°ν•© (μš΄μ˜μ²΄μ œμ— 맞게 μžλ™μœΌλ‘œ)
path = os.path.join('folder', 'subfolder', 'file.txt')
print(path) # folder/subfolder/file.txt (macOS/Linux)

shutil λͺ¨λ“ˆβ€‹

κ³ μˆ˜μ€€ 파일 μž‘μ—…μ„ μœ„ν•œ λͺ¨λ“ˆμž…λ‹ˆλ‹€.

import shutil

# 파일 볡사
shutil.copy('source.txt', 'destination.txt')

# 메타데이터 포함 볡사
shutil.copy2('source.txt', 'destination.txt')

# 폴더 전체 볡사
shutil.copytree('source_folder', 'destination_folder')

# 파일/폴더 이동
shutil.move('old_location.txt', 'new_location.txt')

# 폴더 μ‚­μ œ (λ‚΄μš©λ¬Ό 포함)
shutil.rmtree('folder_to_delete')

폴더 및 파일 생성​

디렉토리 생성​

import os

# 단일 디렉토리 생성
if not os.path.exists('new_folder'):
os.mkdir('new_folder')

# 쀑첩 디렉토리 생성
os.makedirs('parent/child/grandchild', exist_ok=True)
# exist_ok=True: 이미 μ‘΄μž¬ν•΄λ„ μ—λŸ¬ λ°œμƒ μ•ˆ 함

파일 생성​

# 빈 파일 생성
with open('new_file.txt', 'w') as f:
pass

# λ‚΄μš©κ³Ό ν•¨κ»˜ 파일 생성
with open('data.txt', 'w', encoding='utf-8') as f:
f.write('Hello, World!\n')
f.write('Python μžλ™ν™”')

파일 및 폴더 μ‚­μ œβ€‹

import os
import shutil

# 파일 μ‚­μ œ
if os.path.exists('file_to_delete.txt'):
os.remove('file_to_delete.txt')

# 빈 디렉토리 μ‚­μ œ
if os.path.exists('empty_folder'):
os.rmdir('empty_folder')

# λ‚΄μš©μ΄ μžˆλŠ” 디렉토리 μ‚­μ œ
if os.path.exists('folder_with_files'):
shutil.rmtree('folder_with_files')

파일 검색​

os.walk둜 ν•˜μœ„ 폴더 탐색​

import os

# λͺ¨λ“  ν•˜μœ„ ν΄λ”μ˜ 파일 μ°ΎκΈ°
for root, dirs, files in os.walk('/path/to/search'):
for file in files:
if file.endswith('.txt'):
full_path = os.path.join(root, file)
print(full_path)

glob νŒ¨ν„΄ 맀칭​

import glob

# ν˜„μž¬ ν΄λ”μ˜ λͺ¨λ“  .py 파일
python_files = glob.glob('*.py')

# λͺ¨λ“  ν•˜μœ„ ν΄λ”μ˜ .txt 파일
all_txt_files = glob.glob('**/*.txt', recursive=True)

# νŠΉμ • νŒ¨ν„΄μ˜ 파일
data_files = glob.glob('data_*.csv')

# pathlibκ³Ό ν•¨κ»˜ μ‚¬μš©
from pathlib import Path

for file in Path('.').rglob('*.log'):
print(file)

파일 정보 확인​

import os
from datetime import datetime

file_path = 'example.txt'

# 파일 쑴재 확인
exists = os.path.exists(file_path)

# νŒŒμΌμΈμ§€ 디렉토리인지 확인
is_file = os.path.isfile(file_path)
is_dir = os.path.isdir(file_path)

# 파일 크기 (λ°”μ΄νŠΈ)
size = os.path.getsize(file_path)
print(f"크기: {size / 1024:.2f} KB")

# 파일 μˆ˜μ • μ‹œκ°„
mtime = os.path.getmtime(file_path)
modified_date = datetime.fromtimestamp(mtime)
print(f"λ§ˆμ§€λ§‰ μˆ˜μ •: {modified_date}")

# 파일 생성 μ‹œκ°„
ctime = os.path.getctime(file_path)
created_date = datetime.fromtimestamp(ctime)
print(f"생성 μ‹œκ°„: {created_date}")

μ‹€μ „ μ˜ˆμ œβ€‹

1. 사진 정리 μŠ€ν¬λ¦½νŠΈβ€‹

λ‚ μ§œλ³„λ‘œ 사진을 μžλ™μœΌλ‘œ λΆ„λ₯˜ν•©λ‹ˆλ‹€.

import os
import shutil
from datetime import datetime
from pathlib import Path

def organize_photos(source_dir, dest_dir):
"""사진을 λ‚ μ§œλ³„λ‘œ 정리"""

# λͺ©μ μ§€ 폴더 생성
os.makedirs(dest_dir, exist_ok=True)

# 이미지 ν™•μž₯자
image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.heic'}

for file_path in Path(source_dir).rglob('*'):
if file_path.suffix.lower() in image_extensions:
# 파일 μˆ˜μ • λ‚ μ§œ κ°€μ Έμ˜€κΈ°
mtime = os.path.getmtime(file_path)
date = datetime.fromtimestamp(mtime)

# 연도/μ›” 폴더 생성
year_month = date.strftime('%Y/%m')
target_dir = os.path.join(dest_dir, year_month)
os.makedirs(target_dir, exist_ok=True)

# 파일 이동
target_path = os.path.join(target_dir, file_path.name)

# 쀑볡 파일λͺ… 처리
counter = 1
while os.path.exists(target_path):
name, ext = os.path.splitext(file_path.name)
target_path = os.path.join(
target_dir,
f"{name}_{counter}{ext}"
)
counter += 1

shutil.move(str(file_path), target_path)
print(f"이동: {file_path.name} -> {year_month}/")

# μ‚¬μš© 예제
organize_photos('/Users/username/Downloads', '/Users/username/Photos')

2. μžλ™ λ°±μ—… μŠ€ν¬λ¦½νŠΈβ€‹

μ€‘μš”ν•œ νŒŒμΌμ„ λ‚ μ§œλ³„λ‘œ λ°±μ—…ν•©λ‹ˆλ‹€.

import os
import shutil
from datetime import datetime
import zipfile

def backup_files(source_dirs, backup_dir):
"""νŒŒμΌμ„ μ••μΆ•ν•˜μ—¬ λ°±μ—…"""

# λ°±μ—… 폴더 생성
os.makedirs(backup_dir, exist_ok=True)

# λ°±μ—… 파일λͺ… (λ‚ μ§œ 포함)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_name = f"backup_{timestamp}.zip"
backup_path = os.path.join(backup_dir, backup_name)

# ZIP 파일 생성
with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for source_dir in source_dirs:
for root, dirs, files in os.walk(source_dir):
for file in files:
file_path = os.path.join(root, file)
# ZIP λ‚΄ 경둜 μ„€μ •
arcname = os.path.relpath(file_path, os.path.dirname(source_dir))
zipf.write(file_path, arcname)
print(f"λ°±μ—…: {file_path}")

# 파일 크기 확인
size_mb = os.path.getsize(backup_path) / (1024 * 1024)
print(f"\nλ°±μ—… μ™„λ£Œ: {backup_name} ({size_mb:.2f} MB)")

# 였래된 λ°±μ—… μ‚­μ œ (30개 이상 μœ μ§€ μ•ˆ 함)
cleanup_old_backups(backup_dir, keep=30)

def cleanup_old_backups(backup_dir, keep=30):
"""였래된 λ°±μ—… 파일 μ‚­μ œ"""
backups = []

for file in os.listdir(backup_dir):
if file.startswith('backup_') and file.endswith('.zip'):
file_path = os.path.join(backup_dir, file)
mtime = os.path.getmtime(file_path)
backups.append((mtime, file_path))

# μ΅œμ‹  순으둜 μ •λ ¬
backups.sort(reverse=True)

# 였래된 λ°±μ—… μ‚­μ œ
for _, file_path in backups[keep:]:
os.remove(file_path)
print(f"μ‚­μ œ: {os.path.basename(file_path)}")

# μ‚¬μš© 예제
backup_files(
source_dirs=[
'/Users/username/Documents',
'/Users/username/Projects'
],
backup_dir='/Users/username/Backups'
)

3. 둜그 파일 정리​

였래되고 큰 둜그 νŒŒμΌμ„ μžλ™μœΌλ‘œ μ •λ¦¬ν•©λ‹ˆλ‹€.

import os
import gzip
import shutil
from datetime import datetime, timedelta

def cleanup_logs(log_dir, days_to_keep=7, compress_days=3):
"""둜그 파일 정리 및 μ••μΆ•"""

now = datetime.now()

for file in os.listdir(log_dir):
if not file.endswith('.log'):
continue

file_path = os.path.join(log_dir, file)

# 파일 μˆ˜μ • λ‚ μ§œ
mtime = datetime.fromtimestamp(os.path.getmtime(file_path))
age = (now - mtime).days

# 였래된 파일 μ‚­μ œ
if age > days_to_keep:
os.remove(file_path)
print(f"μ‚­μ œ: {file} (생성 ν›„ {age}일)")

# 쀑간 λ‚ μ§œ 파일 μ••μΆ•
elif age > compress_days:
# 이미 μ••μΆ•λœ νŒŒμΌμ€ μŠ€ν‚΅
gz_path = file_path + '.gz'
if os.path.exists(gz_path):
continue

# μ••μΆ•
with open(file_path, 'rb') as f_in:
with gzip.open(gz_path, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)

# 원본 μ‚­μ œ
os.remove(file_path)

# 크기 비ꡐ
original_size = os.path.getsize(file_path)
compressed_size = os.path.getsize(gz_path)
ratio = (1 - compressed_size / original_size) * 100

print(f"μ••μΆ•: {file} (μš©λŸ‰ {ratio:.1f}% κ°μ†Œ)")

# μ‚¬μš© 예제
cleanup_logs('/var/log/myapp', days_to_keep=7, compress_days=3)

4. 파일λͺ… 일괄 변경​

μ—¬λŸ¬ 파일의 이름을 κ·œμΉ™μ— 따라 λ³€κ²½ν•©λ‹ˆλ‹€.

import os
import re

def batch_rename(directory, pattern, replacement):
"""μ •κ·œμ‹ νŒ¨ν„΄μœΌλ‘œ 파일λͺ… 일괄 λ³€κ²½"""

renamed_count = 0

for filename in os.listdir(directory):
# νŒ¨ν„΄ λ§€μΉ­
new_name = re.sub(pattern, replacement, filename)

if new_name != filename:
old_path = os.path.join(directory, filename)
new_path = os.path.join(directory, new_name)

# 쀑볡 λ°©μ§€
if os.path.exists(new_path):
print(f"κ±΄λ„ˆλœ€: {filename} (이미 μ‘΄μž¬ν•¨)")
continue

os.rename(old_path, new_path)
print(f"{filename} -> {new_name}")
renamed_count += 1

print(f"\n총 {renamed_count}개 파일λͺ… λ³€κ²½ μ™„λ£Œ")

def add_prefix(directory, prefix):
"""λͺ¨λ“  νŒŒμΌμ— 접두사 μΆ”κ°€"""
for filename in os.listdir(directory):
old_path = os.path.join(directory, filename)

# λ””λ ‰ν† λ¦¬λŠ” μ œμ™Έ
if os.path.isdir(old_path):
continue

new_name = prefix + filename
new_path = os.path.join(directory, new_name)

os.rename(old_path, new_path)
print(f"{filename} -> {new_name}")

def sequential_numbering(directory, prefix='file', start=1):
"""νŒŒμΌμ„ 순차적으둜 번호 λΆ€μ—¬"""
files = [f for f in os.listdir(directory)
if os.path.isfile(os.path.join(directory, f))]

# 파일 μ •λ ¬
files.sort()

for i, filename in enumerate(files, start=start):
# ν™•μž₯자 뢄리
_, ext = os.path.splitext(filename)

old_path = os.path.join(directory, filename)
new_name = f"{prefix}_{i:03d}{ext}"
new_path = os.path.join(directory, new_name)

os.rename(old_path, new_path)
print(f"{filename} -> {new_name}")

# μ‚¬μš© 예제
# 곡백을 μ–Έλ”μŠ€μ½”μ–΄λ‘œ λ³€κ²½
batch_rename('/path/to/files', r'\s+', '_')

# λ‚ μ§œ ν˜•μ‹ λ³€κ²½ (2023-01-01 -> 20230101)
batch_rename('/path/to/files', r'(\d{4})-(\d{2})-(\d{2})', r'\1\2\3')

# 접두사 μΆ”κ°€
add_prefix('/path/to/files', 'backup_')

# 순차 번호 λΆ€μ—¬
sequential_numbering('/path/to/photos', prefix='photo', start=1)

5. 쀑볡 파일 찾기​

ν•΄μ‹œκ°’μœΌλ‘œ 쀑볡 νŒŒμΌμ„ μ°ΎμŠ΅λ‹ˆλ‹€.

import os
import hashlib
from collections import defaultdict

def file_hash(file_path, chunk_size=8192):
"""파일의 MD5 ν•΄μ‹œ 계산"""
hasher = hashlib.md5()

with open(file_path, 'rb') as f:
while chunk := f.read(chunk_size):
hasher.update(chunk)

return hasher.hexdigest()

def find_duplicates(directory):
"""쀑볡 파일 μ°ΎκΈ°"""

# ν•΄μ‹œκ°’μœΌλ‘œ 파일 κ·Έλ£Ήν™”
hash_to_files = defaultdict(list)

print("파일 μŠ€μΊ” 쀑...")

for root, dirs, files in os.walk(directory):
for filename in files:
file_path = os.path.join(root, filename)

try:
file_size = os.path.getsize(file_path)

# 빈 νŒŒμΌμ€ μ œμ™Έ
if file_size == 0:
continue

# ν•΄μ‹œ 계산
file_hash_value = file_hash(file_path)
hash_to_files[file_hash_value].append((file_path, file_size))

except (OSError, PermissionError) as e:
print(f"였λ₯˜: {file_path} - {e}")

# 쀑볡 파일 좜λ ₯
print("\n쀑볡 파일:")
total_duplicates = 0
total_wasted_space = 0

for file_hash, files in hash_to_files.items():
if len(files) > 1:
print(f"\nκ·Έλ£Ή (ν•΄μ‹œ: {file_hash[:8]}...):")

for file_path, file_size in files:
size_mb = file_size / (1024 * 1024)
print(f" - {file_path} ({size_mb:.2f} MB)")

# 첫 번째 νŒŒμΌμ„ μ œμ™Έν•œ λ‚˜λ¨Έμ§€λŠ” 쀑볡
total_duplicates += len(files) - 1
total_wasted_space += files[0][1] * (len(files) - 1)

if total_duplicates > 0:
wasted_mb = total_wasted_space / (1024 * 1024)
print(f"\n총 {total_duplicates}개의 쀑볡 파일")
print(f"λ‚­λΉ„λ˜λŠ” 곡간: {wasted_mb:.2f} MB")
else:
print("\n쀑볡 파일 μ—†μŒ")

# μ‚¬μš© 예제
find_duplicates('/Users/username/Documents')

pathlib μ‚¬μš©ν•˜κΈ°β€‹

객체 μ§€ν–₯적인 경둜 처리 λ°©μ‹μž…λ‹ˆλ‹€.

from pathlib import Path

# 경둜 생성
path = Path('/Users/username/Documents')

# 파일 쑴재 확인
if path.exists():
print("μ‘΄μž¬ν•¨")

# 디렉토리인지 확인
if path.is_dir():
print("λ””λ ‰ν† λ¦¬μž„")

# 파일 λͺ©λ‘
for file in path.iterdir():
print(file.name)

# glob νŒ¨ν„΄
for txt_file in path.glob('*.txt'):
print(txt_file)

# μž¬κ·€ 검색
for py_file in path.rglob('*.py'):
print(py_file)

# 경둜 κ²°ν•©
new_path = path / 'subfolder' / 'file.txt'

# 파일 읽기/μ“°κΈ°
text_file = Path('example.txt')
text_file.write_text('Hello, World!', encoding='utf-8')
content = text_file.read_text(encoding='utf-8')

# 파일 정보
print(f"크기: {text_file.stat().st_size} bytes")
print(f"ν™•μž₯자: {text_file.suffix}")
print(f"파일λͺ…: {text_file.stem}")
print(f"λΆ€λͺ¨ 디렉토리: {text_file.parent}")

자주 λ¬»λŠ” μ§ˆλ¬Έβ€‹

Q1. os와 pathlib 쀑 μ–΄λ–€ 것을 μ‚¬μš©ν•΄μ•Ό ν•˜λ‚˜μš”?​

A: μƒˆ ν”„λ‘œμ νŠΈμ—λŠ” pathlibλ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€. 더 직관적이고 객체 μ§€ν–₯적이며, 운영체제 κ°„ ν˜Έν™˜μ„±μ΄ μ’‹μŠ΅λ‹ˆλ‹€.

# pathlib 방식 (ꢌμž₯)
from pathlib import Path
path = Path.home() / 'Documents' / 'file.txt'

# os 방식
import os
path = os.path.join(os.path.expanduser('~'), 'Documents', 'file.txt')

Q2. νŒŒμΌμ„ μ‚­μ œν•  λ•Œ νœ΄μ§€ν†΅μœΌλ‘œ 보낼 수 μžˆλ‚˜μš”?​

A: send2trash 라이브러리λ₯Ό μ‚¬μš©ν•˜λ©΄ λ©λ‹ˆλ‹€.

from send2trash import send2trash

# νŒŒμΌμ„ νœ΄μ§€ν†΅μœΌλ‘œ 이동 (μ™„μ „ μ‚­μ œ μ•ˆ 됨)
send2trash('file_to_delete.txt')

Q3. 큰 νŒŒμΌμ„ 볡사할 λ•Œ μ§„ν–‰λ₯ μ„ 보고 μ‹Άμ–΄μš”.​

A: tqdm λΌμ΄λΈŒλŸ¬λ¦¬μ™€ ν•¨κ»˜ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

from tqdm import tqdm
import shutil

def copy_with_progress(src, dst):
total_size = os.path.getsize(src)

with open(src, 'rb') as fsrc:
with open(dst, 'wb') as fdst:
with tqdm(total=total_size, unit='B', unit_scale=True) as pbar:
while chunk := fsrc.read(8192):
fdst.write(chunk)
pbar.update(len(chunk))

copy_with_progress('large_file.dat', 'destination.dat')

Q4. 파일 μž‘μ—… 쀑 μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•˜λ‚˜μš”?​

A: try-except둜 μ˜ˆμ™Έ 처리λ₯Ό ν•΄μ•Ό ν•©λ‹ˆλ‹€.

import os

try:
os.remove('file.txt')
except FileNotFoundError:
print("파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.")
except PermissionError:
print("파일 μ‚­μ œ κΆŒν•œμ΄ μ—†μŠ΅λ‹ˆλ‹€.")
except Exception as e:
print(f"μ•Œ 수 μ—†λŠ” 였λ₯˜: {e}")

Q5. 파일 μž‘μ—…μ΄ μ•ˆμ „ν•œκ°€μš”? μ‹€μˆ˜λ‘œ νŒŒμΌμ„ μ‚­μ œν•  수 μžˆλ‚˜μš”?​

A: λ„€, 쑰심해야 ν•©λ‹ˆλ‹€. λ‹€μŒ μ•ˆμ „ μˆ˜μΉ™μ„ λ”°λ₯΄μ„Έμš”:

import os

def safe_delete(file_path):
"""μ•ˆμ „ν•œ 파일 μ‚­μ œ"""

# 1. 파일 쑴재 확인
if not os.path.exists(file_path):
print(f"파일이 μ—†μŒ: {file_path}")
return False

# 2. μ‚¬μš©μž 확인
response = input(f"정말 μ‚­μ œν•˜μ‹œκ² μŠ΅λ‹ˆκΉŒ? {file_path} (y/n): ")
if response.lower() != 'y':
print("μ·¨μ†Œλ¨")
return False

# 3. λ°±μ—… (선택사항)
backup_path = file_path + '.backup'
shutil.copy2(file_path, backup_path)

# 4. μ‚­μ œ
try:
os.remove(file_path)
print(f"μ‚­μ œ μ™„λ£Œ: {file_path}")
print(f"λ°±μ—… μœ„μΉ˜: {backup_path}")
return True
except Exception as e:
print(f"μ‚­μ œ μ‹€νŒ¨: {e}")
return False

λ‹€μŒ 단계​

파일 μžλ™ν™”λ₯Ό λ°°μ› λ‹€λ©΄, λ‹€μŒ 주제둜 λ„˜μ–΄κ°€μ„Έμš”:

  • μ—‘μ…€ μžλ™ν™”: openpyxl둜 μ—‘μ…€ μž‘μ—… μžλ™ν™”ν•˜κΈ°
  • μ›Ή μžλ™ν™”: Selenium으둜 λΈŒλΌμš°μ € μ œμ–΄ν•˜κΈ°
  • μž‘μ—… μŠ€μΌ€μ€„λ§: 정기적인 μž‘μ—… μžλ™ μ‹€ν–‰ν•˜κΈ°

파일 μž‘μ—… μžλ™ν™”λŠ” 일상적인 업무λ₯Ό 크게 κ°œμ„ ν•  수 μžˆλŠ” κ°•λ ₯ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. μ‹€μ œ 업무에 μ μš©ν•΄λ³΄μ„Έμš”!