Python

Automação com Python: arquivos, pastas e sistema operacional Já leu

11 min de leitura

Automação com Python: arquivos, pastas e sistema operacional
Uma das razões pelas quais Python se tornou tão popular é sua capacidade de automatizar tarefas repetitivas do dia a dia — organizar arquivos, renomear em lote, monitorar pastas, manipular o sistema operacional, executar

Uma das razões pelas quais Python se tornou tão popular é sua capacidade de automatizar tarefas repetitivas do dia a dia — organizar arquivos, renomear em lote, monitorar pastas, manipular o sistema operacional, executar comandos no terminal. O que levaria horas de trabalho manual pode ser resolvido em dezenas de linhas de Python. Neste artigo veremos as principais ferramentas para isso.


os e os.path: Interface com o Sistema Operacional

import os

# Informações do sistema
print(os.name)           # 'posix' (Linux/Mac) ou 'nt' (Windows)
print(os.getcwd())       # diretório atual
print(os.getenv("HOME")) # variável de ambiente

# Navegação
os.chdir("/tmp")         # muda o diretório atual

# Listagem
for item in os.listdir("."):
    print(item)

# Criação e remoção
os.mkdir("nova_pasta")
os.makedirs("a/b/c", exist_ok=True)   # cria toda a árvore
os.rmdir("pasta_vazia")

# Renomeando e movendo
os.rename("antigo.txt", "novo.txt")

# Informações de arquivo
stat = os.stat("arquivo.txt")
print(stat.st_size)      # tamanho em bytes
print(stat.st_mtime)     # timestamp de modificação

# Executando comandos do sistema
codigo = os.system("ls -la")   # retorna código de saída

pathlib: A Forma Moderna

Já vimos pathlib no Artigo 19 — aqui exploramos recursos mais avançados:

from pathlib import Path
from datetime import datetime
import shutil

pasta = Path("/home/usuario/documentos")

# Criando estrutura de pastas
(pasta / "projetos" / "python" / "2024").mkdir(parents=True, exist_ok=True)

# Informações detalhadas
arquivo = Path("relatorio.pdf")
if arquivo.exists():
    stat = arquivo.stat()
    tamanho_mb  = stat.st_size / 1024 / 1024
    modificado  = datetime.fromtimestamp(stat.st_mtime)
    print(f"Tamanho: {tamanho_mb:.2f} MB")
    print(f"Modificado: {modificado.strftime('%d/%m/%Y %H:%M')}")

# Glob — busca por padrão
pasta_atual = Path(".")

# Todos os .py recursivamente
for py in pasta_atual.rglob("*.py"):
    print(py)

# Apenas no nível atual
for txt in pasta_atual.glob("*.txt"):
    print(txt)

# Filtrando por tamanho
grandes = [
    f for f in pasta_atual.rglob("*")
    if f.is_file() and f.stat().st_size > 1024 * 1024   # > 1MB
]

# Leitura e escrita diretas
Path("config.txt").write_text("chave=valor\n", encoding="utf-8")
conteudo = Path("config.txt").read_text(encoding="utf-8")

shutil: Operações de Alto Nível

import shutil
from pathlib import Path

# Copiar arquivo
shutil.copy("origem.txt", "destino.txt")           # copia conteúdo
shutil.copy2("origem.txt", "destino.txt")          # copia + metadados

# Copiar pasta inteira
shutil.copytree("pasta_origem", "pasta_destino")

# Mover
shutil.move("arquivo.txt", "/outro/caminho/")

# Remover pasta com todo o conteúdo
shutil.rmtree("pasta_para_deletar")

# Compactar e descompactar
shutil.make_archive("backup_2024", "zip",  "pasta_fonte")
shutil.make_archive("backup_2024", "gztar", "pasta_fonte")  # .tar.gz
shutil.unpack_archive("backup_2024.zip", "destino")

# Espaço em disco
total, usado, livre = shutil.disk_usage("/")
print(f"Total: {total // (2**30)} GB")
print(f"Usado: {usado  // (2**30)} GB")
print(f"Livre: {livre  // (2**30)} GB")

# Encontrar executável no PATH
python_path = shutil.which("python3")
print(python_path)  # /usr/bin/python3

Organizador Automático de Arquivos

from pathlib import Path
from datetime import datetime
import shutil


REGRAS = {
    "Imagens":    [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp"],
    "Documentos": [".pdf", ".doc", ".docx", ".txt", ".md", ".odt"],
    "Planilhas":  [".xls", ".xlsx", ".csv", ".ods"],
    "Videos":     [".mp4", ".avi", ".mkv", ".mov", ".wmv"],
    "Audios":     [".mp3", ".wav", ".flac", ".aac", ".ogg"],
    "Codigo":     [".py", ".js", ".ts", ".html", ".css", ".json", ".yml"],
    "Compactados":[".zip", ".tar", ".gz", ".rar", ".7z"],
}


def organizar_pasta(caminho: str, simular: bool = True):
    """
    Organiza arquivos de uma pasta em subpastas por tipo.
    simular=True apenas mostra o que faria, sem mover nada.
    """
    pasta   = Path(caminho)
    movidos = []
    ignorados = []

    for arquivo in pasta.iterdir():
        if not arquivo.is_file():
            continue

        extensao  = arquivo.suffix.lower()
        categoria = None

        for cat, exts in REGRAS.items():
            if extensao in exts:
                categoria = cat
                break

        if not categoria:
            categoria = "Outros"

        destino_pasta  = pasta / categoria
        destino_arquivo = destino_pasta / arquivo.name

        # Evita sobrescrever arquivos com mesmo nome
        if destino_arquivo.exists():
            stem  = arquivo.stem
            sufx  = arquivo.suffix
            ts    = datetime.now().strftime("%Y%m%d_%H%M%S")
            destino_arquivo = destino_pasta / f"{stem}_{ts}{sufx}"

        movidos.append({
            "arquivo":  arquivo.name,
            "de":       str(pasta),
            "para":     str(destino_pasta),
            "categoria": categoria
        })

        if not simular:
            destino_pasta.mkdir(exist_ok=True)
            shutil.move(str(arquivo), str(destino_arquivo))

    # Relatório
    print(f"\n{'=== SIMULAÇÃO ===' if simular else '=== EXECUÇÃO ==='}")
    print(f"Pasta: {caminho}")
    print(f"Arquivos a mover: {len(movidos)}\n")

    from collections import Counter
    contagem = Counter(m["categoria"] for m in movidos)
    for cat, qtd in sorted(contagem.items()):
        print(f"  {cat:15}: {qtd} arquivo(s)")

    if not simular:
        print("\nArquivos movidos com sucesso.")

    return movidos


# Primeiro simula, depois executa
movimentos = organizar_pasta("/home/usuario/Downloads", simular=True)
# organizar_pasta("/home/usuario/Downloads", simular=False)

subprocess: Executando Comandos Externos

import subprocess


# Executar e capturar saída
resultado = subprocess.run(
    ["ls", "-la"],
    capture_output=True,
    text=True
)
print(resultado.stdout)
print(resultado.returncode)   # 0 = sucesso


# Com shell=True — permite pipes e redirecionamentos
resultado = subprocess.run(
    "ps aux | grep python | wc -l",
    shell=True,
    capture_output=True,
    text=True
)
print(f"Processos Python: {resultado.stdout.strip()}")


# Verificando erros
try:
    subprocess.run(
        ["git", "status"],
        capture_output=True,
        text=True,
        check=True    # lança CalledProcessError se returncode != 0
    )
except subprocess.CalledProcessError as e:
    print(f"Erro: {e.stderr}")


# Execução com timeout
try:
    resultado = subprocess.run(
        ["ping", "-c", "3", "google.com"],
        capture_output=True,
        text=True,
        timeout=10
    )
    print(resultado.stdout)
except subprocess.TimeoutExpired:
    print("Comando demorou demais.")


# Processo em background
processo = subprocess.Popen(
    ["uvicorn", "main:app", "--port", "8000"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE
)
print(f"PID do servidor: {processo.pid}")
# processo.terminate()  # encerra quando necessário

Monitoramento de Pastas com watchdog

pip install watchdog
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path
from datetime import datetime
import time
import shutil


class ProcessadorArquivos(FileSystemEventHandler):
    """Processa automaticamente arquivos novos em uma pasta."""

    def __init__(self, pasta_entrada: str, pasta_processado: str):
        self.pasta_entrada    = Path(pasta_entrada)
        self.pasta_processado = Path(pasta_processado)
        self.pasta_processado.mkdir(exist_ok=True)

    def on_created(self, event):
        if event.is_directory:
            return
        arquivo = Path(event.src_path)
        print(f"[{datetime.now().strftime('%H:%M:%S')}] Novo arquivo: {arquivo.name}")
        self._processar(arquivo)

    def on_modified(self, event):
        if event.is_directory:
            return
        arquivo = Path(event.src_path)
        print(f"[{datetime.now().strftime('%H:%M:%S')}] Modificado: {arquivo.name}")

    def on_deleted(self, event):
        print(f"[{datetime.now().strftime('%H:%M:%S')}] Deletado: {event.src_path}")

    def _processar(self, arquivo: Path):
        """Processa o arquivo conforme sua extensão."""
        time.sleep(0.5)   # aguarda o arquivo ser completamente escrito

        if not arquivo.exists():
            return

        if arquivo.suffix.lower() == ".csv":
            self._processar_csv(arquivo)
        elif arquivo.suffix.lower() in [".jpg", ".png"]:
            self._processar_imagem(arquivo)
        else:
            self._mover_para_processados(arquivo, "outros")

    def _processar_csv(self, arquivo: Path):
        import csv
        try:
            with open(arquivo, encoding="utf-8") as f:
                reader  = csv.DictReader(f)
                linhas  = list(reader)
            print(f"  CSV processado: {len(linhas)} registros")
            self._mover_para_processados(arquivo, "csv")
        except Exception as e:
            print(f"  Erro ao processar CSV: {e}")

    def _processar_imagem(self, arquivo: Path):
        print(f"  Imagem recebida: {arquivo.stat().st_size / 1024:.1f} KB")
        self._mover_para_processados(arquivo, "imagens")

    def _mover_para_processados(self, arquivo: Path, subcategoria: str):
        destino = self.pasta_processado / subcategoria
        destino.mkdir(exist_ok=True)
        shutil.move(str(arquivo), str(destino / arquivo.name))
        print(f"  Movido para: {destino}")


def monitorar(pasta_entrada: str, pasta_saida: str):
    handler  = ProcessadorArquivos(pasta_entrada, pasta_saida)
    observer = Observer()
    observer.schedule(handler, pasta_entrada, recursive=False)
    observer.start()

    print(f"Monitorando: {pasta_entrada}")
    print("Pressione Ctrl+C para parar.\n")

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


# monitorar("/home/usuario/entrada", "/home/usuario/processado")

Scripts com Argumentos: argparse

import argparse
from pathlib import Path


def criar_parser():
    parser = argparse.ArgumentParser(
        description="Organizador automático de arquivos",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Exemplos:
  python organizar.py ~/Downloads
  python organizar.py ~/Downloads --executar
  python organizar.py ~/Downloads --executar --verbose
  python organizar.py ~/Downloads --extensoes .pdf .doc
        """
    )

    parser.add_argument(
        "pasta",
        type=str,
        help="Caminho da pasta a organizar"
    )
    parser.add_argument(
        "--executar", "-e",
        action="store_true",
        default=False,
        help="Executa as mudanças (padrão: apenas simula)"
    )
    parser.add_argument(
        "--verbose", "-v",
        action="store_true",
        help="Exibe detalhes de cada arquivo"
    )
    parser.add_argument(
        "--extensoes",
        nargs="+",
        metavar="EXT",
        help="Filtra por extensões específicas (ex: .pdf .jpg)"
    )
    parser.add_argument(
        "--destino", "-d",
        type=str,
        default=None,
        help="Pasta de destino (padrão: subpastas dentro da origem)"
    )

    return parser


def main():
    parser = criar_parser()
    args   = parser.parse_args()

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

    print(f"Pasta:    {pasta}")
    print(f"Modo:     {'EXECUÇÃO' if args.executar else 'SIMULAÇÃO'}")
    if args.extensoes:
        print(f"Filtro:   {', '.join(args.extensoes)}")

    movimentos = organizar_pasta(str(pasta), simular=not args.executar)

    if args.verbose:
        for m in movimentos:
            print(f"  {m['arquivo']:30} → {m['categoria']}")


if __name__ == "__main__":
    main()
# Uso
python organizar.py ~/Downloads
python organizar.py ~/Downloads --executar --verbose
python organizar.py ~/Downloads --extensoes .pdf .doc --destino ~/Documentos

Exemplo Completo: Script de Backup Automatizado

#!/usr/bin/env python3
"""
backup.py — Script de backup com compressão e rotação

Uso:
    python backup.py --origem ~/projetos --destino ~/backups
    python backup.py --origem ~/projetos --destino ~/backups --manter 7
"""
import argparse
import shutil
import os
from pathlib import Path
from datetime import datetime


def criar_backup(origem: Path, destino: Path, manter: int = 7) -> Path:
    """Cria backup comprimido e remove backups antigos."""

    destino.mkdir(parents=True, exist_ok=True)

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    nome_base = f"backup_{origem.name}_{timestamp}"
    caminho   = destino / nome_base

    print(f"[{datetime.now().strftime('%H:%M:%S')}] Criando backup...")
    print(f"  Origem:  {origem}")
    print(f"  Destino: {caminho}.zip")

    shutil.make_archive(str(caminho), "zip", str(origem.parent), str(origem.name))

    arquivo_zip = Path(f"{caminho}.zip")
    tamanho     = arquivo_zip.stat().st_size / 1024 / 1024
    print(f"  Tamanho: {tamanho:.2f} MB")
    print(f"  Status:  OK ✓")

    # Rotação — remove backups mais antigos
    backups = sorted(destino.glob(f"backup_{origem.name}_*.zip"))
    if len(backups) > manter:
        for antigo in backups[:-manter]:
            antigo.unlink()
            print(f"  Removido (rotação): {antigo.name}")

    return arquivo_zip


def main():
    parser = argparse.ArgumentParser(description="Script de backup automatizado")
    parser.add_argument("--origem",  required=True, help="Pasta a fazer backup")
    parser.add_argument("--destino", required=True, help="Onde salvar os backups")
    parser.add_argument("--manter",  type=int, default=7, help="Backups a manter (padrão: 7)")
    args = parser.parse_args()

    origem  = Path(args.origem).expanduser().resolve()
    destino = Path(args.destino).expanduser().resolve()

    if not origem.exists():
        print(f"Erro: origem não encontrada: {origem}")
        exit(1)

    arquivo = criar_backup(origem, destino, args.manter)

    # Log de auditoria
    log = destino / "backup.log"
    with open(log, "a", encoding="utf-8") as f:
        f.write(f"{datetime.now().isoformat()} | {origem} | {arquivo} | OK\n")

    print(f"\nBackup concluído: {arquivo}")


if __name__ == "__main__":
    main()
# Executar manualmente
python backup.py --origem ~/projetos --destino ~/backups --manter 5

# Agendar com cron (Linux/Mac)
# crontab -e
# 0 2 * * * /usr/bin/python3 /home/usuario/backup.py --origem ~/projetos --destino ~/backups

Resumo

  • os oferece interface de baixo nível com o sistema operacional — variáveis de ambiente, processos, permissões
  • pathlib.Path é a forma moderna e multiplataforma de trabalhar com caminhos e arquivos
  • shutil realiza operações de alto nível — copiar, mover, compactar e verificar espaço em disco
  • subprocess.run() executa comandos externos com controle de saída, erros e timeout
  • watchdog monitora o sistema de arquivos em tempo real — ideal para pipelines de processamento automático
  • argparse transforma scripts em ferramentas de linha de comando com documentação integrada
  • Scripts de automação devem ter modo de simulação antes de executar mudanças irreversíveis
  • Sempre use expanduser() e resolve() para normalizar caminhos fornecidos pelo usuário

Referências e Leituras Complementares

  • pathlib — documentação oficial — https://docs.python.org/3/library/pathlib.html
  • shutil — documentação oficial — https://docs.python.org/3/library/shutil.html
  • subprocess — documentação oficial — https://docs.python.org/3/library/subprocess.html
  • argparse — documentação oficial — https://docs.python.org/3/library/argparse.html
  • watchdog — monitoramento de arquivos — https://watchdog.readthedocs.io/en/latest/
  • SWEIGART, Al. Automate the Boring Stuff with Python. 2. ed. No Starch Press, 2019. Cap. 8–10 — automação de arquivos, pastas e processos com exemplos práticos.
  • SWEIGART, Al. Beyond the Basic Stuff with Python. No Starch Press, 2020. Cap. 16 — scripts de linha de comando profissionais com Python.
Comentários

Mais em Python

Artigo 43 — Testes com pytest: fixtures, mocks e cobertura
Artigo 43 — Testes com pytest: fixtures, mocks e cobertura

Código sem testes é código que você não confia. Testes automatizados são a re...

Listas: criação, manipulação e métodos
Listas: criação, manipulação e métodos

Listas são a estrutura de dados mais utilizada em Python. Elas permite...

Artigo 44 — Qualidade de Código: linting, type hints e mypy
Artigo 44 — Qualidade de Código: linting, type hints e mypy

Testes verificam o que o código faz. Ferramentas de qualidade verificam como...