여러 CSV를 합치고 원본 파일명 남기기
열 구조가 같은 CSV를 순서대로 합치고 source_file 열을 붙입니다. 쉼표가 있는 품목명, 누락된 열, 기존 출력도 작은 데이터로 확인합니다.
하위 폴더까지 읽어 파일 경로·확장자·크기·수정 시각을 표로 남깁니다. 작은 예제 4개로 시작하고 원본과 기존 결과를 보존합니다.
이런 분께폴더 안의 문서 수와 위치를 손으로 정리해 온 Python 입문자
이 작업의 완료 기준은 폴더가 만들어졌다는 사실만이 아닙니다. CSV에 원본 파일 네 개가 각각 한 번씩 들어가고, 표시된 바이트 합계가 63이며, 원본 내용은 그대로여야 합니다. 먼저 동봉된 자료로 이 기준을 확인한 다음 자신의 업무 폴더로 확장하세요.
python example.py코드는 터미널의 현재 폴더가 아니라 example.py가 저장된 위치를 출발점으로 삼습니다. BASE 아래의 sample_files만 조사하므로 예제 폴더를 통째로 옮겨도 상대적인 배치가 같으면 동작합니다. outputs는 조사 대상 밖에 두어 결과 CSV가 다시 입력 목록에 섞이지 않게 했습니다.
| 열 | 뜻 | 읽을 때 확인할 점 |
|---|---|---|
| relative_path | sample_files에서 시작하는 상대 경로 | 같은 이름도 폴더가 다르면 구별됩니다. |
| extension | 마지막 확장자를 소문자로 표시 | 확장자가 없으면 (없음), tar.gz라면 .gz입니다. |
| size_bytes | 파일 크기를 바이트 정수로 표시 | 0은 빈 파일이며 누락을 뜻하지 않습니다. |
| modified_utc | UTC 기준 파일 수정 시각 | 끝의 +00:00은 UTC를 뜻하며 한국 시각과 다릅니다. |
relative_path는 운영체제가 달라도 알아보기 쉽도록 슬래시로 표시합니다. 결과는 이 경로 기준으로 정렬됩니다. 파일을 탐색하는 내부 순서에 기대지 않기 때문에 같은 입력의 행 순서를 비교하기 쉽습니다.
collect_files는 정보를 모으는 역할, main은 새 CSV를 쓰는 역할을 맡습니다. 입력 확인이 끝나기 전에 outputs를 만들지 않습니다. 읽을 수 없는 폴더나 링크를 만나면 일부 목록을 성공한 결과처럼 저장하지 않고 중지합니다.
"""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 폴더 자체의 존재 여부를 확인합니다.
| relative_path | extension | size_bytes |
|---|---|---|
| docs/agenda.txt | .txt | 15 |
| empty.txt | .txt | 0 |
| readme.txt | .txt | 17 |
| reports/sales.csv | .csv | 31 |
위 표의 합계는 15 + 0 + 17 + 31 = 63바이트입니다. 터미널에도 ‘완료: 4개 파일, 합계 63바이트’가 표시됩니다. CSV의 첫 줄은 열 이름이므로 스프레드시트에서 헤더를 포함하면 총 다섯 행이 보입니다. empty.txt를 빼고 세는 실수를 주의하세요.
실패 조건도 정상적인 설계의 일부입니다. 다시 실행이 막혔다고 출력 거부 코드를 지우지 마세요. 이전 결과가 필요하면 outputs를 날짜가 들어간 다른 이름으로 옮긴 다음 재실행하거나, ZIP을 새 폴더에 풀어 새 실험을 시작하면 됩니다.
처음에는 실제 업무 폴더 전체를 연결하지 말고 대표 파일 몇 개를 sample_files의 복사본에 넣어 보세요. 파일명이 한글이거나 공백을 포함해도 경로 객체로 처리합니다. 단, 다른 위치로 연결되는 심볼릭 링크나 연결 디렉터리는 지원 대상에서 제외하므로 일반 파일로 구성된 작은 사본이 적합합니다.
입력 폴더가 비어 있으면 헤더만 있는 CSV가 생성되고 파일 수는 0으로 표시됩니다. 이는 폴더가 없어서 중지되는 경우와 다릅니다. 빈 폴더도 정상 업무 상태일 수 있으므로 결과 해석에서 구별하세요.
| 보이는 현상 | 가능한 원인 | 다음 행동 |
|---|---|---|
| python 명령을 찾지 못함 | 실행기 설치 또는 경로 설정 문제 | python --version을 먼저 확인하고 Windows에서는 py를 시도합니다. |
| sample_files는 실제 폴더여야 함 | 폴더 누락 또는 링크 사용 | ZIP 전체를 풀었는지 확인하고 실제 폴더를 복구합니다. |
| outputs가 이미 있음 | 이전 실행 결과가 남음 | 기존 결과를 옮겨 보관한 뒤 새로 실행합니다. |
| 한글 표시가 깨짐 | 열기 프로그램의 인코딩 해석 문제 | CSV 가져오기에서 UTF-8을 지정하고 원본은 수정하지 않습니다. |
| 읽기 권한 오류 | 접근 불가 폴더 또는 잠긴 경로 | 접근 가능한 연습용 사본으로 먼저 범위를 줄입니다. |
이 결과는 실행하는 동안 관찰한 파일 정보입니다. 파일 내용이 같은지 비교하는 중복 검사나 복구 가능한 백업은 아닙니다. 동일한 파일 크기를 가진 문서 두 개도 내용은 다를 수 있습니다. 해시 비교가 필요하면 별도의 목적과 열을 추가해야 합니다.
파일을 조사하는 중에 다른 프로그램이 이름이나 내용을 바꾸는 경우, 네트워크 드라이브의 지연, 매우 많은 파일을 한꺼번에 메모리에 모으는 경우는 이 입문 예제에서 다루지 않습니다. 출력 중 디스크 쓰기가 실패하면 불완전한 outputs가 남을 수 있으므로 완료 문구와 결과 행 수를 함께 확인하세요.
2026-09-19 · Windows 11 · CPython 3.12.14 · 추가 패키지 없음 · 배포본의 임시 복사본에서 실행
코드·입력 데이터·실행 안내가 포함되어 있습니다. 압축을 풀고 README.txt부터 읽어 주세요.
예제 ZIP 다운로드직접 작성한 연습 자료 · 원본을 따로 보관한 뒤 실행하세요.
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.