Curso de Python / Automatizar cópias de arquivos com prévia e sem sobrescrever

Comece uma automação de arquivos separando a prévia das operações, chamada de dry-run, da execução efetiva. Nesta prática, o programa copia arquivos de texto para outra pasta, preserva as origens e recusa destinos existentes; a cópia só acontece com --apply.

Preparar uma pasta de prática e uma política de cópia

Use uma pasta nova, separada dos seus documentos de trabalho, e abra o terminal nela. Os caminhos relativos copy-demo/in e copy-demo/out são interpretados a partir da pasta atual do terminal, não necessariamente da pasta do script. Confira essa localização antes de executar; a aula sobre caminhos com pathlib explica essa diferença.

A ferramenta procura somente arquivos correspondentes a *.txt diretamente na origem. Não entra em subpastas e exclui entradas que sejam links simbólicos. Não renomeia, move nem apaga os arquivos originais. Em caso de colisão, a política é parar em vez de sobrescrever o destino ou inventar outro nome.

Primeiro prepare uma amostra. Salve o código Python completo abaixo como setup_copy.py, em UTF-8, e execute python setup_copy.py no ambiente virtual selecionado. No macOS ou Ubuntu, fora de um ambiente virtual, use python3 se esse for o comando do seu interpretador. Todos os módulos desta aula pertencem à biblioteca padrão.

from pathlib import Path
source = Path("copy-demo/in")
source.mkdir(parents=True, exist_ok=True)
with (source / "note.txt").open("x", encoding="utf-8") as f:
    f.write("demo\n")
print("sample ready")

A saída deve ser sample ready. Esta etapa cria copy-demo/in/note.txt com o texto demo e uma quebra de linha. Execute a preparação apenas uma vez nessa pasta: o modo x recusa um arquivo já existente. Preparar a amostra grava dados; isso é independente do dry-run da ferramenta de cópia, que ainda não foi executado.

Conferir a prévia sem criar o destino

Na mesma pasta, salve o programa completo como safe_copy.py, em UTF-8. O argumento --apply é opcional e fica desativado por padrão. Antes da cópia, o programa exige que a origem seja uma pasta e ordena os nomes selecionados para produzir uma sequência previsível.

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, target = Path("copy-demo/in"), Path("copy-demo/out")
if not source.is_dir():
    parser.error("prepare copy-demo/in first")
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)
        if hashlib.sha256(src.read_bytes()).digest() != hashlib.sha256(dst.read_bytes()).digest():
            raise RuntimeError("copy verification failed")
print("applied" if args.apply else "dry-run")

Depois de preparar a amostra, execute python safe_copy.py. Saída inicial esperada:

copy note.txt
dry-run

copy note.txt descreve a operação planejada; não significa que o arquivo já foi copiado. Confira que copy-demo/out ainda não existe. A mensagem final dry-run confirma o modo de prévia. Se você não preparou a origem ou abriu o terminal em outra pasta, a mensagem prepare copy-demo/in first indica que a pasta esperada não foi encontrada.

Uma origem existente e vazia produz apenas dry-run, sem criar o destino. A verificação de colisões também funciona na prévia: um destino já existente pode interromper a listagem antes da mensagem final.

Aplicar a cópia e comparar o conteúdo

Após conferir o diretório de trabalho e a lista da prévia, execute:

python safe_copy.py --apply

Na primeira aplicação, a saída é copy note.txt seguida de applied. O arquivo copy-demo/out/note.txt passa a existir, e o original permanece em copy-demo/in/note.txt. Abra os dois e confirme o texto. O programa compara também seus hashes SHA-256, valores calculados a partir dos bytes para detectar uma diferença de conteúdo.

O destino é aberto com xb: escrita binária com criação exclusiva. Mesmo depois da verificação inicial de existência, essa abertura recusa um arquivo que já esteja no mesmo caminho. shutil.copyfileobj transfere os dados entre os arquivos abertos, e o bloco with fecha ambos antes da leitura usada para verificar os hashes. Veja cópia de conteúdo com shutil.copyfileobj e os modos de abertura de arquivos.

Execute novamente com --apply: agora deve ocorrer FileExistsError, preservando o resultado anterior. A mensagem é esperada pela política adotada, não um convite para trocar o modo para wb. Se quiser repetir a prática desde o início, prefira outra pasta vazia e prepare uma nova amostra.

O programa para no primeiro erro. Com vários arquivos, alguns podem ter sido copiados antes de uma colisão ou falha posterior; a operação inteira não é uma transação com reversão automática. Uma falha durante a transferência também pode deixar um destino parcial. Confira quais arquivos foram criados e preserve esse diagnóstico antes de definir a recuperação.

Reconhecer limites e planejar uma ampliação

A comparação do exemplo lê cada arquivo inteiro com read_bytes, sendo adequada à pequena amostra. Para arquivos grandes, calcule hashes por blocos. A igualdade de conteúdo não comprova a preservação de permissões, proprietário, datas ou todos os outros metadados; esta ferramenta copia bytes.

A exclusão de links simbólicos nas entradas não transforma o programa em uma ferramenta protegida contra alterações concorrentes de diretórios feitas por usuários não confiáveis. O exemplo pressupõe uma pasta de prática sob seu controle e não implementa proteção completa para esse cenário.

  1. Repita a prévia antes da primeira aplicação e confirme que ela não criou o destino.
  2. Depois da aplicação, confira a existência das duas cópias e a igualdade do conteúdo. Repita o comando para verificar a recusa de sobrescrita.
  3. Em uma nova pasta de prática, acrescente um arquivo de outra extensão e uma subpasta com um texto. Confira que eles não entram na cópia direta de *.txt.
  4. Antes de acrescentar renomeação, movimentação ou exclusão, defina uma lista de alvos, a política de colisões, o backup e a recuperação de falhas parciais. Para uma eventual limpeza, confirme os caminhos absolutos resolvidos e remova apenas resultados descartáveis dentro da pasta criada para o exercício.