🌗 ← Índice

🐍 Python P22 — Projeto: Renomeador de Arquivos (CLI)


Data: 14/08/2026

Nível: Projeto (Mês 3 — Projetos Reais)




🎯 Objetivos





🏗️ O Projeto: `renomeador.py`


Casos de uso



python renomeador.py /tmp/fotos --prefixo "viagem_"
# IMG_001.jpg → viagem_IMG_001.jpg

python renomeador.py /tmp/docs --substituir "antigo" "novo"
# relatorio_antigo.pdf → relatorio_novo.pdf

python renomeador.py /tmp/backups --numerar
# backup.tar.gz → backup_01.tar.gz, backup_02.tar.gz...

python renomeador.py /tmp/docs --substituir "antigo" "novo" --seco
# [simulado] relatorio_antigo.pdf → relatorio_novo.pdf

Estrutura



renomeador/
├── renomeador.py     # CLI principal
└── test_renomeador.py # testes

`renomeador.py` (versão completa)



import argparse
import re
from pathlib import Path


def aplicar_regras(nome_original: str, args) -> str:
    """Aplica prefixo/sufixo/substituição ao nome (sem extensão)."""
    caminho = Path(nome_original)
    stem, ext = caminho.stem, caminho.suffix

    if args.substituir:
        antigo, novo = args.substituir
        stem = stem.replace(antigo, novo)

    if args.regex:
        padrao, novo = args.regex
        stem = re.sub(padrao, novo, stem)

    if args.prefixo:
        stem = f"{args.prefixo}{stem}"

    if args.sufixo:
        stem = f"{stem}{args.sufixo}"

    return f"{stem}{ext}"


def numerar(arquivos: list[Path], digitos=2) -> dict[Path, Path]:
    """Mapeia nome original → nome numerado."""
    mapeamento = {}
    for i, arquivo in enumerate(arquivos, start=1):
        novo = arquivo.with_name(f"{arquivo.stem}_{i:0{digitos}d}{arquivo.suffix}")
        mapeamento[arquivo] = novo
    return mapeamento


def main():
    parser = argparse.ArgumentParser(description="Renomeador de arquivos em massa")
    parser.add_argument("pasta", type=Path, help="Pasta com os arquivos")
    parser.add_argument("--prefixo", default=None, help="Prefixo a adicionar")
    parser.add_argument("--sufixo", default=None, help="Sufixo a adicionar")
    parser.add_argument("--substituir", nargs=2, metavar=("ANTIGO", "NOVO"), help="Substitui texto")
    parser.add_argument("--regex", nargs=2, metavar=("PADRAO", "NOVO"), help="Substitui via regex")
    parser.add_argument("--numerar", action="store_true", help="Numera os arquivos")
    parser.add_argument("--seco", action="store_true", help="Simula sem renomear")
    parser.add_argument("--extensao", default=None, help="Filtra por extensão (ex: jpg)")
    args = parser.parse_args()

    if not args.pasta.is_dir():
        parser.error(f"Pasta não encontrada: {args.pasta}")

    if not any([args.prefixo, args.sufixo, args.substituir, args.regex, args.numerar]):
        parser.error("Informe pelo menos uma regra (--prefixo, --sufixo, --substituir, --regex, --numerar)")

    arquivos = [p for p in args.pasta.iterdir() if p.is_file()]
    if args.extensao:
        arquivos = [p for p in arquivos if p.suffix.lower() == f".{args.extensao.lower()}"]

    if args.numerar:
        mapeamento = numerar(arquivos)
    else:
        mapeamento = {p: p.with_name(aplicar_regras(p.name, args)) for p in arquivos}

    alterados = 0
    for origem, destino in mapeamento.items():
        if origem == destino:
            continue
        if args.seco:
            print(f"[simulado] {origem.name} → {destino.name}")
        else:
            origem.rename(destino)
            print(f"{origem.name} → {destino.name}")
        alterados += 1

    print(f"\nTotal: {alterados} arquivo(s) {'(simulado)' if args.seco else 'renomeado(s)'}")


if __name__ == "__main__":
    main()

Testes (`test_renomeador.py`)



import pytest
from renomeador import aplicar_regras, numerar
from pathlib import Path


class Args:
    substituir = ("antigo", "novo")
    regex = None
    prefixo = None
    sufixo = None


def test_substituir_texto():
    args = Args()
    assert aplicar_regras("relatorio_antigo.pdf", args) == "relatorio_novo.pdf"


def test_prefixo():
    args = Args()
    args.substituir = None
    args.prefixo = "viagem_"
    assert aplicar_regras("IMG_001.jpg", args) == "viagem_IMG_001.jpg"


def test_regex():
    args = Args()
    args.substituir = None
    args.regex = (r"\d+", "NUM")
    assert aplicar_regras("foto_123.jpg", args) == "foto_NUM.jpg"


def test_numerar():
    arquivos = [Path(f"/tmp/backup_{i}.tar.gz") for i in range(1, 4)]
    mapeamento = numerar(arquivos)
    nomes = [str(p) for p in mapeamento.values()]
    assert "backup_1_01.tar.gz" in nomes
    assert len(mapeamento) == 3



✍️ Exercícios


  1. 1. Rode com `--seco` numa pasta de teste e confira que nada foi alterado.
  2. 2. Adicione uma regra `--maiusculas` que converte o nome para caixa alta.
  3. 3. Teste `--substituir` com texto que aparece várias vezes no nome.



🧪 Desafio


Adicione a opção --data que prefixa os arquivos com a data atual (YYYY-MM-DD_). Combine com --numerar para backups diários numerados:



python renomeador.py /tmp/backups --data --numerar
# dados.sql → 2026-08-14_dados_01.sql



✅ Correção (desafio)



# no main(), adicione:
parser.add_argument("--data", action="store_true", help="Prefixa com a data atual")

# e na montagem do nome:
from datetime import datetime

if args.data:
    args.prefixo = f"{datetime.now().strftime('%Y-%m-%d')}_{args.prefixo or ''}"

💡 Resultado: dados.sql → 2026-08-14_dados.sql — padrão ideal para backups diários automáticos (combine com cron!).





HB Estudos · ← Voltar ao índice de Python