스크립트·파일 자동화

폴더 속 파일 목록을 CSV로 정리하기

하위 폴더까지 읽어 파일 경로·확장자·크기·수정 시각을 표로 남깁니다. 작은 예제 4개로 시작하고 원본과 기존 결과를 보존합니다.

목차 보기

이런 분께폴더 안의 문서 수와 위치를 손으로 정리해 온 Python 입문자

준비사항
  • Python 3.12 이상을 준비하고 터미널에서 python --version으로 버전을 확인합니다.
  • 예제 ZIP을 새 폴더에 압축 해제합니다. 압축 파일 안에서 직접 실행하지 않습니다.
  • example.py가 보이는 폴더에서 터미널을 엽니다. Windows에서 python 명령이 없으면 py, macOS·Linux에서는 환경에 따라 python3를 사용합니다.
  • 외부 패키지 설치나 계정은 필요하지 않습니다. 동봉된 자료는 직접 작성한 합성 데이터입니다.

01파일 네 개로 첫 목록 만들기

이 작업의 완료 기준은 폴더가 만들어졌다는 사실만이 아닙니다. CSV에 원본 파일 네 개가 각각 한 번씩 들어가고, 표시된 바이트 합계가 63이며, 원본 내용은 그대로여야 합니다. 먼저 동봉된 자료로 이 기준을 확인한 다음 자신의 업무 폴더로 확장하세요.

  1. 압축 해제한 폴더에서 example.py, README.txt, sample_files 폴더를 찾습니다.
  2. sample_files를 열어 readme.txt와 empty.txt, docs 및 reports 하위 폴더를 확인합니다. empty.txt는 의도적으로 비어 있습니다.
  3. 터미널에서 아래 명령을 한 번 실행합니다. 실행 중 입력 파일을 이동하거나 편집하지 않습니다.
  4. 완료 문구와 출력 경로가 보이면 outputs/file_list.csv를 엽니다.
bash
python example.py

02경로와 열의 의미 이해하기

코드는 터미널의 현재 폴더가 아니라 example.py가 저장된 위치를 출발점으로 삼습니다. BASE 아래의 sample_files만 조사하므로 예제 폴더를 통째로 옮겨도 상대적인 배치가 같으면 동작합니다. outputs는 조사 대상 밖에 두어 결과 CSV가 다시 입력 목록에 섞이지 않게 했습니다.

읽을 때 확인할 점
relative_pathsample_files에서 시작하는 상대 경로같은 이름도 폴더가 다르면 구별됩니다.
extension마지막 확장자를 소문자로 표시확장자가 없으면 (없음), tar.gz라면 .gz입니다.
size_bytes파일 크기를 바이트 정수로 표시0은 빈 파일이며 누락을 뜻하지 않습니다.
modified_utcUTC 기준 파일 수정 시각끝의 +00:00은 UTC를 뜻하며 한국 시각과 다릅니다.

relative_path는 운영체제가 달라도 알아보기 쉽도록 슬래시로 표시합니다. 결과는 이 경로 기준으로 정렬됩니다. 파일을 탐색하는 내부 순서에 기대지 않기 때문에 같은 입력의 행 순서를 비교하기 쉽습니다.

03실행에 쓰는 전체 코드

collect_files는 정보를 모으는 역할, main은 새 CSV를 쓰는 역할을 맡습니다. 입력 확인이 끝나기 전에 outputs를 만들지 않습니다. 읽을 수 없는 폴더나 링크를 만나면 일부 목록을 성공한 결과처럼 저장하지 않고 중지합니다.

example.py
"""sample_files를 읽어 outputs/file_list.csv에 목록을 기록합니다. 원본은 변경하지 않습니다."""

import csv
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

BASE = Path(__file__).resolve().parent
INPUT = BASE / "sample_files"
OUTPUT = BASE / "outputs"
FIELDS = ["relative_path", "extension", "size_bytes", "modified_utc"]


def is_link(path):
    # Windows 연결 디렉터리(junction)도 Python 3.12 이상에서는 거부합니다.
    return path.is_symlink() or getattr(path, "is_junction", lambda: False)()


def collect_files(folder):
    if is_link(folder) or not folder.is_dir():
        raise ValueError("sample_files는 실제 폴더여야 합니다.")
    rows = []

    def stop_on_error(error):
        raise error

    # 하위 폴더까지 읽되, 링크를 따라 다른 폴더로 나가지 않습니다.
    for root, dirs, files in os.walk(folder, followlinks=False, onerror=stop_on_error):
        root = Path(root)
        for name in dirs + files:
            if is_link(root / name):
                raise ValueError(f"링크는 처리하지 않습니다: {root / name}")
        for name in sorted(files):
            path = root / name
            if not path.is_file():
                raise ValueError(f"일반 파일이 아닙니다: {path}")
            stat = path.stat()
            rows.append({
                "relative_path": path.relative_to(folder).as_posix(),
                "extension": path.suffix.lower() or "(없음)",
                "size_bytes": stat.st_size,
                "modified_utc": datetime.fromtimestamp(
                    stat.st_mtime, timezone.utc
                ).isoformat(timespec="seconds"),
            })
    return sorted(rows, key=lambda row: row["relative_path"])


def main():
    if OUTPUT.exists() or OUTPUT.is_symlink():
        raise FileExistsError("outputs가 이미 있습니다. 기존 결과를 옮긴 뒤 실행하세요.")
    rows = collect_files(INPUT)  # 모두 읽은 뒤에만 출력 폴더를 만듭니다.
    OUTPUT.mkdir()
    target = OUTPUT / "file_list.csv"
    # x는 기존 파일을 덮어쓰지 않는 모드입니다. BOM은 한글 CSV 열기에 도움을 줍니다.
    with target.open("x", encoding="utf-8-sig", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(rows)
    print(f"완료: {len(rows)}개 파일, 합계 {sum(row['size_bytes'] for row in rows)}바이트")
    print(target)
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except (OSError, ValueError) as error:
        print(f"중지: {error}", file=sys.stderr)
        raise SystemExit(2)

CSV는 csv.DictWriter가 열 구분과 따옴표 처리를 맡습니다. newline을 빈 문자열로 주고 UTF-8 BOM을 붙여 저장합니다. 출력 파일은 x 모드로 열어 같은 파일이 이미 생긴 경우에도 덮어쓰지 않습니다. 이 예제에서는 그보다 먼저 outputs 폴더 자체의 존재 여부를 확인합니다.

04결과 네 행과 합계 맞추기

relative_pathextensionsize_bytes
docs/agenda.txt.txt15
empty.txt.txt0
readme.txt.txt17
reports/sales.csv.csv31

위 표의 합계는 15 + 0 + 17 + 31 = 63바이트입니다. 터미널에도 ‘완료: 4개 파일, 합계 63바이트’가 표시됩니다. CSV의 첫 줄은 열 이름이므로 스프레드시트에서 헤더를 포함하면 총 다섯 행이 보입니다. empty.txt를 빼고 세는 실수를 주의하세요.

05목록뿐 아니라 원본 보존도 확인하기

  1. CSV의 파일 경로 네 개를 sample_files의 실제 위치와 하나씩 대조합니다. docs와 reports 폴더 자체는 데이터 행으로 들어가지 않습니다.
  2. 빈 파일이 0바이트로 포함되었는지, 확장자가 .txt와 .csv로 나뉘었는지 확인합니다.
  3. sample_files/readme.txt를 열어 원래의 Sample inventory 문장이 남아 있는지 봅니다.
  4. 같은 명령을 다시 실행합니다. ‘outputs가 이미 있습니다’라는 중지가 나와야 하며 첫 결과는 유지되어야 합니다.

실패 조건도 정상적인 설계의 일부입니다. 다시 실행이 막혔다고 출력 거부 코드를 지우지 마세요. 이전 결과가 필요하면 outputs를 날짜가 들어간 다른 이름으로 옮긴 다음 재실행하거나, ZIP을 새 폴더에 풀어 새 실험을 시작하면 됩니다.

06내 폴더에 적용하는 순서

처음에는 실제 업무 폴더 전체를 연결하지 말고 대표 파일 몇 개를 sample_files의 복사본에 넣어 보세요. 파일명이 한글이거나 공백을 포함해도 경로 객체로 처리합니다. 단, 다른 위치로 연결되는 심볼릭 링크나 연결 디렉터리는 지원 대상에서 제외하므로 일반 파일로 구성된 작은 사본이 적합합니다.

  1. 기존 예제 입력과 결과를 보관하고 연습용 폴더를 새로 만듭니다.
  2. 파일 수를 손으로 셀 수 있는 5~10개를 sample_files에 배치합니다. 하위 폴더도 한 단계 넣어 봅니다.
  3. 목록을 생성하고 상대 경로가 기대한 위치를 가리키는지 확인합니다.
  4. 확인한 규칙을 그대로 유지한 채 대상 파일 수를 늘립니다. 파일 크기는 내용 길이와 다를 수 있으므로 바이트 단위를 유지합니다.

입력 폴더가 비어 있으면 헤더만 있는 CSV가 생성되고 파일 수는 0으로 표시됩니다. 이는 폴더가 없어서 중지되는 경우와 다릅니다. 빈 폴더도 정상 업무 상태일 수 있으므로 결과 해석에서 구별하세요.

07자주 만나는 오류 해결하기

보이는 현상가능한 원인다음 행동
python 명령을 찾지 못함실행기 설치 또는 경로 설정 문제python --version을 먼저 확인하고 Windows에서는 py를 시도합니다.
sample_files는 실제 폴더여야 함폴더 누락 또는 링크 사용ZIP 전체를 풀었는지 확인하고 실제 폴더를 복구합니다.
outputs가 이미 있음이전 실행 결과가 남음기존 결과를 옮겨 보관한 뒤 새로 실행합니다.
한글 표시가 깨짐열기 프로그램의 인코딩 해석 문제CSV 가져오기에서 UTF-8을 지정하고 원본은 수정하지 않습니다.
읽기 권한 오류접근 불가 폴더 또는 잠긴 경로접근 가능한 연습용 사본으로 먼저 범위를 줄입니다.

08이 목록이 보장하는 범위

이 결과는 실행하는 동안 관찰한 파일 정보입니다. 파일 내용이 같은지 비교하는 중복 검사나 복구 가능한 백업은 아닙니다. 동일한 파일 크기를 가진 문서 두 개도 내용은 다를 수 있습니다. 해시 비교가 필요하면 별도의 목적과 열을 추가해야 합니다.

파일을 조사하는 중에 다른 프로그램이 이름이나 내용을 바꾸는 경우, 네트워크 드라이브의 지연, 매우 많은 파일을 한꺼번에 메모리에 모으는 경우는 이 입문 예제에서 다루지 않습니다. 출력 중 디스크 쓰기가 실패하면 불완전한 outputs가 남을 수 있으므로 완료 문구와 결과 행 수를 함께 확인하세요.

실행·검증 기록

2026-09-19 · Windows 11 · CPython 3.12.14 · 추가 패키지 없음 · 배포본의 임시 복사본에서 실행

  • 중첩 폴더 포함 4개 파일과 합계 63바이트 확인
  • 빈 입력 폴더는 헤더만 출력
  • 없는 입력 폴더와 기존 outputs 거부
  • 다른 작업 폴더에서 실행해 경로 독립성 확인
  • 입력 파일의 실행 전후 SHA-256 동일 확인
검증 범위의 한계
  • macOS·Linux에서는 실행하지 않았습니다.
  • 압축 해제 후 수정 시각은 달라질 수 있습니다.
  • 동시 파일 변경이나 디스크 용량 부족 상황은 테스트하지 않았습니다.

사이트 전체의 작성·검증 원칙

직접 실행할 예제 파일

코드·입력 데이터·실행 안내가 포함되어 있습니다. 압축을 풀고 README.txt부터 읽어 주세요.

예제 ZIP 다운로드

직접 작성한 연습 자료 · 원본을 따로 보관한 뒤 실행하세요.

참고 출처

설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.