フォルダー内のファイル一覧をCSVにまとめる
サブフォルダーまで調べ、ファイルのパス、拡張子、サイズ、更新時刻を表に記録します。小さなサンプルファイル4個から始め、元のファイルと既存の結果を保管します。
変更前と変更後の名前を表で確認し、名前の衝突を検査します。--applyを明示したときだけ新しいフォルダーにコピーを作り、元のファイルを残します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 한국어
対象読者複数のファイル名を変えたいものの、ルールの間違いや上書きが心配な初心者
一括変更では、一つのファイルの誤字より先に、誤ったルールが全ファイルに適用される事態を防ぐ必要があります。この例では、名前の一覧をすべて検証してから計画を表示します。通常のコマンドはファイルのコピーもフォルダーの作成もしません。実際の処理は、確認後に--applyを付けて別途実行します。
python example.py| old_name | new_name |
|---|---|
| 메모 초안.txt | note-draft.txt |
| 견적 1.txt | quote-001.txt |
| 사진 설명.txt | photo-notes.txt |
対応表の各行は、一つの元ファイルと一つの結果を結び付けます。old_nameは、大文字・小文字も含めて実際の名前と完全に一致させます。元ファイルを漏らしたり、存在しない名前を追加したりすると、処理全体が停止します。このルールにより、フォルダーに新しいファイルが増えているのに古い対応表をそのまま使う間違いを検出できます。
new_name同士は、大文字・小文字だけが違う場合も衝突と見なします。report.txtとREPORT.txtを別々の結果として作らないための方針です。名前の前後の空白、末尾のピリオド、パス区切り文字、Windowsの予約名も拒否します。命名ルールが業務に適しているかは、プレビューで人が最終確認する必要があります。
make_planですべての入力と衝突を検査します。mainは検証済みの計画を表示してから、--applyの有無を確認します。実際に適用する場合も、元の名前をrenameするのではなく、内容を新しい名前でコピーするため、開始時の状態を再確認できます。
"""기본은 미리보기입니다. --apply일 때만 새 이름의 복사본을 만듭니다."""
import argparse
import csv
import re
import shutil
import sys
from pathlib import Path
BASE = Path(__file__).resolve().parent
INPUT = BASE / "source_files"
MAPPING = BASE / "mapping.csv"
OUTPUT = BASE / "outputs"
RESERVED = {"CON", "PRN", "AUX", "NUL"} | {
f"{prefix}{number}" for prefix in ("COM", "LPT") for number in range(1, 10)
}
def validate_name(name):
# 폴더 경로, Windows 예약 이름, 제어 문자 등을 이름으로 받지 않습니다.
if (not name or name in {".", ".."} or name != name.strip()
or name.endswith(".") or re.search(r'[<>:"/\\|?*\x00-\x1f]', name)
or name.split(".")[0].upper() in RESERVED):
raise ValueError(f"사용할 수 없는 파일명: {name!r}")
def make_plan():
if (INPUT.is_symlink() or not INPUT.is_dir()
or getattr(INPUT, "is_junction", lambda: False)()):
raise ValueError("source_files는 실제 폴더여야 합니다.")
if MAPPING.is_symlink():
raise ValueError("mapping.csv 링크는 처리하지 않습니다.")
sources = list(INPUT.iterdir())
if any(path.is_symlink() or not path.is_file() for path in sources):
raise ValueError("source_files에는 일반 파일만 넣으세요.")
if not sources:
raise ValueError("source_files에 파일이 없습니다.")
plan, old_keys, new_keys = [], set(), set()
with MAPPING.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream, strict=True)
if reader.fieldnames != ["old_name", "new_name"]:
raise ValueError("mapping.csv의 열은 old_name,new_name이어야 합니다.")
for row in reader:
if None in row or any(value is None for value in row.values()):
raise ValueError(f"mapping.csv {reader.line_num}행의 열 수를 확인하세요.")
old, new = row["old_name"], row["new_name"]
validate_name(old)
validate_name(new)
if old.casefold() in old_keys:
raise ValueError(f"원본이 중복 지정되었습니다: {old}")
if new.casefold() in new_keys:
raise ValueError(f"새 이름 충돌: {new}")
old_keys.add(old.casefold())
new_keys.add(new.casefold())
plan.append((old, new))
# 한 파일이라도 누락되거나 없는 파일을 지정하면 아무것도 복사하지 않습니다.
if {old for old, _ in plan} != {path.name for path in sources}:
raise ValueError("mapping.csv의 old_name은 원본 파일 전체와 정확히 일치해야 합니다.")
if OUTPUT.exists() or OUTPUT.is_symlink():
raise FileExistsError("outputs가 이미 있습니다. 기존 결과를 옮긴 뒤 실행하세요.")
return plan
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--apply", action="store_true", help="새 이름의 복사본을 outputs/renamed에 만듭니다.")
args = parser.parse_args()
plan = make_plan() # 충돌 검사는 출력 폴더를 만들기 전에 전체에 대해 끝냅니다.
for old, new in plan:
print(f"{old} → {new}")
if not args.apply:
print(f"미리보기: {len(plan)}개. 파일은 변경되지 않았습니다.")
print("이름을 확인한 뒤 python example.py --apply 를 실행하세요.")
return 0
OUTPUT.mkdir()
renamed = OUTPUT / "renamed"
renamed.mkdir()
for old, new in plan:
# 이름 변경 대신 복사합니다. xb는 대상이 생겼으면 덮어쓰지 않고 실패합니다.
with (INPUT / old).open("rb") as source, (renamed / new).open("xb") as target:
shutil.copyfileobj(source, target)
with (OUTPUT / "manifest.csv").open("x", encoding="utf-8-sig", newline="") as stream:
writer = csv.writer(stream)
writer.writerow(["old_name", "new_name"])
writer.writerows(plan)
print(f"완료: {len(plan)}개 복사본 → {renamed}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, ValueError, csv.Error) as error:
print(f"중지: {error}", file=sys.stderr)
raise SystemExit(2)
argparseのstore_trueオプションを使っているため、--applyを省略すると通常のプレビューになります。コピー先はxbモードで開き、対象ファイルがすでに作られている場合は上書きしません。完了した対応関係はmanifest.csvにも記録し、新しい名前から元の名前を逆引きできるようにしています。
プレビューの三行を確認したら、同じフォルダーで次のコマンドを実行します。プレビュー後にmapping.csvを変更した場合は、通常のコマンドを再実行して新しい計画を確認してください。適用コマンドは前のプレビュー画面を記憶しているのではなく、実行時点の対応表を読み直して検査します。
python example.py --apply| 場所 | 期待される結果 |
|---|---|
| source_files/ | 元の名前のファイル3個を保持 |
| outputs/renamed/ | note-draft.txt, quote-001.txt, photo-notes.txt |
| outputs/manifest.csv | 列名の後に、元の名前→新しい名前の対応3行 |
ターミナルには「완료: 3개 コピー본」と出力フォルダーのパスが表示されます。新しいファイルは名前だけでなく、内容も一つずつ開いて確認します。たとえば、note-draft.txt内の練習用文書番号は、메모 초안.txtの番号と一致する必要があります。
コピーのループ内で一つずつ検証すると、最初のファイルを処理してから二つ目で失敗する可能性があります。この例ではすべての名前を先に検査し、事前検証のエラーが部分的な適用につながらないようにしています。既存のoutputsがある場合も、開始前に停止します。
業務に適用するときは、一つのフォルダーの小さなコピーから始めます。source_filesに元ファイルのコピーを入れ、mapping.csvにファイル数と同じだけ行を作ります。拡張子を含む完全な名前を書く必要があります。拡張子を変えてもファイル形式は変換されないため、テキストの内容に対して名前だけを.pdfにするような対応表は作らないでください。
番号を付けるなら001、002のように桁数をそろえ、日付も同じ順序で書くと探しやすくなります。名前に含める項目を先に決め、プレビューで順序と漏れを確認してください。この例は、文書の内容からタイトルを抽出したり、利用者の意図を推測して名前を自動生成したりしません。
| メッセージ | 確認する点 | 解決方法 |
|---|---|---|
| 새 이름 충돌(新しい名前の衝突) | new_nameに同じ値、または大文字・小文字だけが異なる値がある | 互いに区別できる最終的な名前を指定します。 |
| 원본이 중복 지정됨(元ファイルの重複指定) | old_nameが二行に繰り返されている | 元ファイルごとに一行だけ残します。 |
| 원본 파일 전체와 일치해야 함(すべての元ファイルとの一致が必要) | ファイルの漏れ、またはold_nameの誤字 | フォルダー内の一覧と対応表を照合し直します。 |
| 사용할 수 없는 파일명(使用できないファイル名) | パス、予約名、禁止文字、末尾のピリオド | ファイル名だけを記入し、使用できない文字を除きます。 |
| outputsがすでに存在する | 前回のコピー結果 | 結果を別名にして保管し、やり直します。 |
適用後に同じコマンドを繰り返しても、既存の結果は更新しません。プレビューも既存のoutputsを見つけると停止します。別の作業の結果が混ざらないためのルールであり、上書きオプションは用意していません。
この例は元ファイルを読み取るだけで、名前を変えるのはコピーです。元ファイルを保持したうえで、コピー分の保存容量も必要です。文書の内容は保持しますが、作成・更新時刻、アクセス権限、OSのすべての追加メタデータまで同一に複製するツールではありません。
衝突など、コピー前に見つかるエラーでは全体を停止します。ただし、コピー開始後の電源断やディスク容量不足まで、単一のトランザクションのように元に戻すわけではありません。その場合はoutputsに一部のファイルが残る可能性があるため、完了メッセージと三つのファイルを確認してください。他のプログラムが元ファイルや対応表を同時に変更する場合も、別の設計が必要です。
2026-09-19 · Windows 11 · CPython 3.12.14 · 追加パッケージなし · 配布版の一時コピーで実行
コード、入力データ、実行手順が含まれています。展開して、まずREADME.txtを読んでください。
サンプルZIPをダウンロードサンプルコード、ファイル名、入力キーは原文のままです。翻訳本文のコマンドと確認手順も併せて参照してください。
独自に作成した練習用資料 · 元のファイルを別に保管してから実行してください。
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。