Python não serve só para web apps e machine learning. No terminal, é uma navalha suíça que substitui grep, awk, sed e curl: tudo ao mesmo tempo, com uma legibilidade que o Bash nunca terá.

Quando comecei como sysadmin, o meu dia era um loop de comandos: grep | awk | sort | uniq -c | sort -rn. Funcionava, mas cada pipeline era um castelo de cartas. Um espaço a mais, uma regex mal escapada, e o output ia para o lixo.

Python mudou isso. Com re, pathlib, subprocess e argparse, consigo fazer em 30 linhas o que antes eram 5 pipes frágeis. E o código fica legível, reutilizável e com tratamento de erros a sério.

Vou mostrar-te:

  • Parsing de logs com regex e o módulo re
  • Web scraping com requests + BeautifulSoup
  • Automação de tarefas com shutil, pathlib e subprocess
  • CLIs profissionais com argparse e click
  • Boas práticas: type hints, logging, virtualenvs
  • Exemplo prático: parse de logs Apache para CSV

Python para Sysadmin — terminal

⬆ Script de parsing de logs Apache em Python com output colorido e argumentos CLI


Parsing de logs com re

Os logs são o pão nosso de cada dia do sysadmin. Em Bash, fazes grep | cut | sort. Em Python, fazes uma regex que captura logo os campos todos:

import re

# Log Apache combinado: IP - - [data] "METHOD URL PROTO" STATUS BYTES "REFERER" "UA"
LOG_PATTERN = re.compile(
    r'^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+) (\d+|-) "([^"]*)" "([^"]*)"'
)

def parse_log_line(line: str) -> dict | None:
    """Parseia uma linha de log Apache. Devolve dict ou None."""
    match = LOG_PATTERN.match(line)
    if not match:
        return None

    return {
        "ip": match.group(1),
        "data": match.group(2),
        "metodo": match.group(3),
        "url": match.group(4),
        "status": int(match.group(5)),
        "bytes": int(match.group(6)) if match.group(6) != "-" else 0,
    }
Copy

Dica: Compila a regex com re.compile() uma vez, fora do loop. É muito mais rápido do que compilar de novo para cada linha. Num ficheiro com 100k linhas, a diferença é de segundos para minutos.


Web scraping com requests + beautifulsoup

Precisas de monitorizar uma página que não tem API? Python é o teu aliado:

import requests
from bs4 import BeautifulSoup

def check_cert_expiry(url: str) -> str | None:
    """Verifica data de expiração de SSL via página de status."""
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()

        soup = BeautifulSoup(resp.text, 'html.parser')
        cert_table = soup.find('table', class_='cert-details')
        if cert_table and cert_table.find('tr'):
            return cert_table.find_all('td')[3].text.strip()
    except (requests.RequestException, AttributeError) as e:
        logger.error(f"Erro ao aceder a {url}: {e}")
        return None
Copy

Automação de tarefas: shutil, pathlib, subprocess

O trio que substitui a maioria dos comandos de shell em Python. pathlib para caminhos, shutil para operações de ficheiros, subprocess para comandos externos.

from pathlib import Path
import shutil
import subprocess
import logging

logger = logging.getLogger(__name__)

def rotate_logs(log_dir: str, max_days: int = 30) -> list[Path]:
    """Remove logs mais velhos que max_days."""
    removed = []
    for f in Path(log_dir).glob("*.log*"):
        age_days = (Path.now() - f.stat().st_mtime).days
        if age_days > max_days:
            f.unlink(missing_ok=True)
            logger.info(f"Removido log antigo: {f.name}")
            removed.append(f)
    return removed

def compress_logs(log_dir: str) -> bool:
    """Comprime logs não compactados com gzip."""
    for f in Path(log_dir).glob("access.log.*[0-9]"):
        if f.suffix != '.gz':
            with open(f, 'rb') as src:
                shutil.copyfileobj(src, subprocess.Popen(
                    ['gzip'], stdin=subprocess.PIPE, stdout=subprocess.PIPE
                ).stdin)
            f.unlink()
            logger.info(f"Comprimido: {f.name}")
    return True
Copy

Atenção: Prefere subprocess.run() com check=True em vez de os.system(). O primeiro lança exceção se o comando falhar, o segundo engole erros silenciosamente.


CLI com argparse e click

Um bom script de sysadmin precisa de argumentos. O argparse vem incluído no Python e é mais que suficiente para 90% dos casos:

import argparse

def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Analisa logs Apache e exporta para CSV",
        epilog="Ex: python3 log_analyzer.py access.log -o relatorio.csv --top 20"
    )
    parser.add_argument('logfile', help="Caminho para o ficheiro de log")
    parser.add_argument('-o', '--output', default='relatorio.csv')
    parser.add_argument('--top', type=int, default=10)
    parser.add_argument('-v', '--verbose', action='store_true')
    return parser.parse_args()

args = parse_args()
print(f"A analisar {args.logfile}, top {args.top} IPs...")
Copy

Para algo mais declarativo, o click é fantástico:

import click

@click.command()
@click.argument('logfile')
@click.option('-o', '--output', default='relatorio.csv')
@click.option('--top', default=10, type=int)
@click.option('-v', '--verbose', is_flag=True)
def analyze(logfile, output, top, verbose):
    """Analisa logs Apache e exporta CSV com os IPs mais frequentes."""
    click.echo(f"📊 A analisar {logfile}...")
Copy

Boas práticas para scripts de sysadmin

PráticaPorquê
if __name__ == "__main__"Permite importar funções sem executar o script. Essencial para testes e reutilização
Type hintsDocumentam o código e permitem deteção de erros com mypy/pyright
logging em vez de printNíveis (INFO, ERROR, DEBUG), output para ficheiro, formatação consistente
VirtualenvsIsolam dependências. Usa python -m venv .venv e pip install -r requirements.txt
pathlib em vez de os.pathAPI moderna, orientada a objetos, mais legível e menos propensa a erros

Exemplo prático: analisador de logs Apache

"""log_analyzer.py — Analisa logs Apache, extrai IPs e exporta CSV.

Uso:
    python3 log_analyzer.py /var/log/apache2/access.log -o top_ips.csv --top 20
"""
import logging
import csv
import re
from collections import Counter
from pathlib import Path
import argparse

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S"
)
logger = logging.getLogger(__name__)

# Regex para log combinado Apache
LOG_RE = re.compile(
    r'^(\S+) \S+ \S+ \[([^\]]+)\] "\S+ (\S+) \S+" (\d+) \S+'
)

def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('logfile', type=Path, help="Ficheiro de log Apache")
    parser.add_argument('-o', '--output', type=Path, default=Path('top_ips.csv'))
    parser.add_argument('--top', type=int, default=10)
    parser.add_argument('-v', '--verbose', action='store_true')
    return parser.parse_args()

def count_ips(logfile: Path) -> Counter[str]:
    """Lê o log e devolve um Counter de IPs."""
    counter: Counter[str] = Counter()
    with open(logfile, 'r') as f:
        for line in f:
            match = LOG_RE.match(line)
            if match:
                counter[match.group(1)] += 1
    logger.info(f"Processadas {sum(counter.values())} linhas, "
                 f"{len(counter)} IPs únicos")
    return counter

def export_csv(counter: Counter[str], output: Path, top_n: int) -> None:
    """Exporta top N IPs para CSV."""
    with open(output, 'w', newline='') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(['IP', 'Requests'])
        for ip, count in counter.most_common(top_n):
            writer.writerow([ip, count])
    logger.info(f"CSV exportado: {output} ({top_n} linhas)")

def main() -> None:
    args = parse_args()
    if args.verbose:
        logger.setLevel(logging.DEBUG)

    if not args.logfile.exists():
        logger.error(f"Ficheiro não encontrado: {args.logfile}")
        raise SystemExit(1)

    counter = count_ips(args.logfile)
    export_csv(counter, args.output, args.top)

    # Mostra top 5 no terminal
    print("\n🏆 Top 5 IPs:")
    for ip, count in counter.most_common(5):
        print(f"  {ip:20s} → {count:5d} requests")

if __name__ == "__main__":
    main()
Copy
Saída do script:
$ python3 log_analyzer.py access.log --top 20
14:32:01 [INFO] Processadas 45231 linhas, 389 IPs únicos
14:32:01 [INFO] CSV exportado: top_ips.csv (20 linhas)

🏆 Top 5 IPs:
  192.168.1.100        →  4521 requests
  10.0.0.45            →  3210 requests
  203.0.113.42         →  2899 requests
  198.51.100.77        →  1543 requests
  192.0.2.88           →  1201 requests
Copy

Recapitulando

  • Logs: re.compile() + named groups para parsing rápido e legível
  • Web scraping: requests + BeautifulSoup para monitorização sem API
  • Automação: pathlib + shutil + subprocess.run(check=True)
  • CLI: argparse para o básico, click para algo mais declarativo
  • Boas práticas: type hints, logging, __name__ == "__main__", virtualenvs
  • Exemplo completo: parser de logs Apache com output CSV

Python é a ferramenta mais versátil que um sysadmin pode ter na caixa. A curva de aprendizagem é curta, o retorno é enorme.

Pega num script teu que uses grep | awk | sort e reescreve-o em Python. Vais ver que fica mais legível, mais robusto e mais fácil de manter.

Recursos adicionais

Comentários (0)

Nenhum comentário ainda. Seja o primeiro!

Deixar comentário