[Python] os, shutil, glob: 파일 시스템 도구
python os, shutil, glob, 파일 시스템, os.path
정의
파일 시스템 다루는 세 표준 모듈:
os: OS 인터페이스 (환경 변수, 프로세스, 파일 기본 연산)os.path: 경로 문자열 조작 (대부분pathlib로 대체 가능)shutil: 고수준 파일 복사·이동·트리 작업glob: 와일드카드 매칭
pathlib.Path(py-pathlib 참고)가 객체지향 대안. 같은 작업을 어느 쪽으로든 가능하니 팀 컨벤션 맞춤.
os 자주 쓰는 것
환경 변수
import os
os.environ["API_KEY"] # KeyError if missing
os.environ.get("API_KEY", "default")
os.environ["DEBUG"] = "1" # 자식 프로세스에도 상속
# 현재 모든 환경
for k, v in os.environ.items():
print(k, v)
현재 디렉터리
os.getcwd()
os.chdir("/path/to/dir")
파일/디렉터리 작업
os.listdir(".") # 디렉터리 이름 리스트
os.scandir(".") # 더 빠른 iterator (DirEntry 객체)
os.mkdir("newdir") # 단일 디렉터리
os.makedirs("a/b/c", exist_ok=True) # 중간 디렉터리 자동 생성
os.remove("file.txt") # 파일 삭제
os.rmdir("empty_dir") # 빈 디렉터리만
os.unlink("file.txt") # remove와 동일
os.rename("old", "new")
os.replace("src", "dst") # 덮어쓰기 보장 (rename은 OS에 따라 다름)
# 메타데이터
st = os.stat("file.txt")
print(st.st_size, st.st_mtime)
권한과 사용자
os.chmod("file.txt", 0o644)
os.chown("file.txt", uid, gid) # Unix만
os.getuid(), os.getgid() # Unix
os.getpid(), os.getppid()
프로세스
os.system("ls -la") # 셸 실행 (subprocess 권장)
os.execvp("ls", ["ls", "-la"]) # 현재 프로세스를 교체
os.fork() # 자식 프로세스 (Unix만)
os.wait()
대부분의 프로세스 작업은 subprocess로 (py-subprocess 참고).
os.path 핵심
import os.path as p
p.join("/home", "user", "file.txt") # '/home/user/file.txt'
p.basename("/a/b/c.txt") # 'c.txt'
p.dirname("/a/b/c.txt") # '/a/b'
p.split("/a/b/c.txt") # ('/a/b', 'c.txt')
p.splitext("file.tar.gz") # ('file.tar', '.gz')
p.exists("/a/b")
p.isfile("..."); p.isdir("...")
p.abspath(".") # 절대 경로
p.expanduser("~/data") # 홈 디렉터리 확장
p.expandvars("$HOME/data") # 환경 변수 확장
p.normpath("/a/./b//c") # '/a/b/c'
p.relpath("/a/b/c", "/a") # 'b/c'
pathlib.Path가 더 가독성 좋지만, 빠른 일회성 처리엔 os.path가 가볍다.
shutil: 고수준 파일 작업
복사
import shutil
shutil.copy(src, dst) # 데이터 + 권한 (mtime은 X)
shutil.copy2(src, dst) # 데이터 + 권한 + mtime
shutil.copyfile(src, dst) # 데이터만
shutil.copymode(src, dst) # 권한만
shutil.copystat(src, dst) # 모든 메타데이터
shutil.copytree(src, dst, dirs_exist_ok=True) # 디렉터리 트리
이동·삭제
shutil.move(src, dst) # rename + 다른 파일시스템 이동
shutil.rmtree(path) # 디렉터리 트리 통째로
shutil.rmtree(path, ignore_errors=True)
rmtree는 위험. path를 신중히 확인 후 호출.
디스크 사용량
total, used, free = shutil.disk_usage("/")
print(f"{free / 1e9:.1f}GB free")
압축
shutil.make_archive("backup", "gztar", root_dir="src") # backup.tar.gz
shutil.unpack_archive("backup.tar.gz", "extracted")
명령어 경로 찾기
shutil.which("git") # '/usr/local/bin/git' 또는 None
subprocess.run(["git", ...]) 호출 전에 명령어 존재 확인.
glob: 와일드카드 매칭
import glob
glob.glob("*.py") # 현재 디렉터리의 .py
glob.glob("**/*.py", recursive=True) # 재귀
glob.glob("[abc]*.txt") # a, b, c로 시작하는 .txt
glob.iglob("**/*.py", recursive=True) # iterator (메모리 절약)
패턴 문법:
*: 0개 이상 글자 (디렉터리 구분자 제외)?: 1글자[abc]/[!abc]: 문자 집합 / 부정**(recursive=True): 임의 깊이 디렉터리
대용량 디렉터리는 pathlib.Path.rglob 또는 os.scandir 직접 사용이 더 빠를 수 있다.
tempfile: 임시 파일
import tempfile
from pathlib import Path
# 임시 파일
with tempfile.NamedTemporaryFile(suffix=".txt", delete=True) as f:
f.write(b"hello")
f.flush()
print(f.name)
# 자동 삭제
# 임시 디렉터리
with tempfile.TemporaryDirectory() as tmp:
tmp = Path(tmp)
work_file = tmp / "data.json"
work_file.write_text("...")
# 자동 정리
# 단일 호출
fd, path = tempfile.mkstemp() # 호출자가 close, 삭제 책임
fileinput: 파일·stdin 통합 처리
import fileinput
# python script.py file1 file2 또는 cat file | python script.py
for line in fileinput.input():
print(line.rstrip().upper())
스트림 처리 스크립트에 편함. inplace=True로 in-place 편집도.
stat 결과 해석
import os
import stat
st = os.stat("file.txt")
print(st.st_size) # 바이트
print(st.st_mtime) # mtime (epoch float)
# 모드 검사
print(stat.S_ISREG(st.st_mode)) # 일반 파일?
print(stat.S_ISDIR(st.st_mode))
print(stat.S_ISLNK(st.st_mode))
print(oct(st.st_mode & 0o777)) # 권한 비트만
자주 보는 패턴
디렉터리 트리 순회
import os
for root, dirs, files in os.walk("project"):
# root: 현재 디렉터리
# dirs: root 안의 서브디렉터리 (in-place 수정 가능: 트리 제외)
# files: root 안의 파일
if ".git" in dirs:
dirs.remove(".git") # .git 안 들어감
for f in files:
if f.endswith(".py"):
print(os.path.join(root, f))
pathlib.Path.rglob이 더 간결.
안전한 파일 쓰기 (atomic write)
import os
import tempfile
def atomic_write(path, content):
"""쓰기 중 중단되어도 원본 파일 보존."""
dir_ = os.path.dirname(path) or "."
fd, tmp = tempfile.mkstemp(dir=dir_)
try:
with os.fdopen(fd, "w") as f:
f.write(content)
os.replace(tmp, path) # atomic rename
except Exception:
os.unlink(tmp)
raise
os.replace는 같은 파일시스템 내에서 원자성 보장 (POSIX).
대용량 파일 복사 (진행 상황)
import shutil
def copy_with_progress(src, dst, callback):
total = os.path.getsize(src)
copied = 0
with open(src, "rb") as r, open(dst, "wb") as w:
while chunk := r.read(64 * 1024):
w.write(chunk)
copied += len(chunk)
callback(copied / total)
# shutil.copyfileobj도 사용 가능 (구현이 더 최적화됨)
함정
1. 권한 모드 8진수
os.chmod("file", 644) # 10진 644 → 비정상 권한
os.chmod("file", 0o644) # 8진 (rw-r--r--)
리눅스 권한은 항상 0o 접두로.
2. os.path.join 안 쓰고 문자열 concat
path = base + "/" + name # 윈도우에서 / 와 \ 혼용 위험
path = os.path.join(base, name) # 또는 Path(base) / name
3. rmtree에 신뢰할 수 없는 경로
shutil.rmtree(user_input) # 위험! 검증 필요
Path(user_input).resolve().is_relative_to(allowed_root) 검증 후 실행.
4. open()이 누수
f = open("file.txt")
data = f.read()
# f.close() 빠뜨림 → 파일 핸들 누수
# 항상 with
with open("file.txt") as f:
data = f.read()
💬 댓글