Python Tutorial / Search and Copy Files with a Dry Run and Verification

Start batch file automation by separating a preview of intended changes from the apply step. This exercise copies text files into a new location and refuses to overwrite an existing destination.

Define the target and preservation policy

Use a fresh practice folder. The example works under copy-demo/in and copy-demo/out, does not recursively search subfolders and excludes symbolic-link entries. It preserves source files rather than moving or deleting them.

Moving affects the source as well as the destination. Separating copy, content verification and later cleanup makes failures easier to assess. This example stops on a name collision instead of silently inventing a new filename.

For the relevant APIs and platform requirements, see Search and Copy Files with a Dry Run and Verification documentation.

Inspect the default dry run

Save safe_copy.py as UTF-8 and run python safe_copy.py in the project environment. The program creates its disposable sample input when absent, but does not copy it without --apply.

import argparse
import hashlib
import shutil
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument("--apply", action="store_true")
args = parser.parse_args()
source = Path("copy-demo/in")
target = Path("copy-demo/out")
source.mkdir(parents=True, exist_ok=True)
sample = source / "note.txt"
if not sample.exists():
    sample.write_text("demo\n", encoding="utf-8")
files = sorted(p for p in source.glob("*.txt") if p.is_file() and not p.is_symlink())
for src in files:
    dst = target / src.name
    if dst.exists() or dst.is_symlink():
        raise FileExistsError(dst)
    print("copy", src.name)
    if args.apply:
        target.mkdir(parents=True, exist_ok=True)
        with src.open("rb") as incoming, dst.open("xb") as outgoing:
            shutil.copyfileobj(incoming, outgoing)
        digest = lambda p: hashlib.sha256(p.read_bytes()).digest()
        if digest(src) != digest(dst):
            raise RuntimeError("copy verification failed")
print("applied" if args.apply else "dry-run")

Initial output:

copy note.txt
dry-run

Apply the reviewed copy and verify the bytes

After reviewing the target list, execute:

python safe_copy.py --apply

The first apply prints copy note.txt and applied, and creates copy-demo/out/note.txt. Opening the target with xb rejects an existing file. SHA-256 comparison checks that the small example’s source and destination bytes agree.

A second apply should raise FileExistsError and preserve the prior result. A failed copy can leave a partial destination; inspect its resolved path and content before deciding whether it is safe to remove and retry.

Know the limits before expanding the tool

The hash implementation reads each small file into memory. Large files should be hashed in chunks. Byte equality does not verify access-control rules, all metadata or symbolic-link semantics, and this example does not defend against an untrusted user concurrently replacing parent directories.

Before adding renaming or deletion, design target inventories, collision detection, backups and recovery. For cleanup, verify that resolved absolute targets remain inside the disposable copy-demo directory and remove only the intended results. The pathlib lesson explains how working-directory choices affect this scope.