Expressões regulares (regex) são padrões de busca utilizados para localizar, extrair e manipular texto de forma poderosa. No contexto do shell Linux/Bash, elas são essenciais para processamento de texto em arquivos, logs, saídas de comandos e automação de tarefas. Nesta aula, vamos mergulhar nas duas principais variações de regex usadas no shell: as básicas (BRE) e as estendidas (ERE), e como elas se aplicam aos utilitários clássicos grep, sed e awk.

Dominar regex no shell é um divisor de águas: o que antes exigia múltiplos comandos ou scripts complexos pode ser resolvido com uma única expressão bem elaborada. Ao final desta aula, você terá compreensão sólida das diferenças entre BRE e ERE, saberá quando cada uma é necessária e verá exemplos práticos que poderá aplicar imediatamente.

Básicas vs estendidas

As expressões regulares no Linux seguem dois padrões principais: BRE (Basic Regular Expressions) e ERE (Extended Regular Expressions). A diferença fundamental está na sintaxe dos metacaracteres: em BRE, certos caracteres como ?, +, {, }, (, ) e | têm significado literal, a menos que sejam escapados com uma barra invertida (ex.: \?). Em ERE, esses mesmos caracteres têm significado especial sem necessidade de escape.

Na prática, isso significa que em BRE, para usar repetição opcional (?), uma ou mais vezes (+), ou alternância (|), você precisa escrever \?, \+, \|. Já em ERE, escreve-se simplesmente ?, + e |. Por exemplo, a expressão colou?r em ERE corresponde a "color" ou "colour"; em BRE, deve ser escrita como colou\?r. Essa diferença pode causar confusão, mas lembrar que ERE é mais intuitiva ajuda.

Outra diferença é o suporte a intervalos: {n,m} (quantificador) é suportado em ERE sem escape, e em BRE com escape (\{n,m\}). Grupos de captura também seguem o mesmo princípio: em ERE, (abc) agrupa; em BRE, \(abc\).

A maioria das ferramentas modernas do Linux, como grep com a opção -E, sed com -E, e awk por padrão, utilizam ERE. No entanto, é crucial conhecer BRE, pois muitos scripts antigos e algumas ferramentas (como o grep sem opções) ainda a usam por padrão.

Para ilustrar, vejamos um exemplo simples: procurar linhas que contenham "error" ou "warning" em um arquivo de log.

# Usando BRE (grep padrão) - é necessário escapar o pipe
$ grep 'error\|warning' /var/log/syslog

# Usando ERE (grep -E) - sem escape
$ grep -E 'error|warning' /var/log/syslog

Ambos produzem o mesmo resultado, mas a sintaxe é diferente. A partir de agora, quando você vir comandos com \|, saiba que é BRE; com |, é ERE.

Em grep, sed, awk

As três ferramentas mais comuns para processamento de texto no shell são grep, sed e awk. Cada uma tem suas particularidades no suporte a regex.

grep é o filtro por excelência: ele lê linhas de entrada e imprime aquelas que correspondem a um padrão. Por padrão, usa BRE; com -E, usa ERE; com -P, usa PCRE (Perl Compatible Regular Expressions), que é ainda mais poderoso e similar ao que você encontra em linguagens como Python. O grep é ideal para buscas simples e rápidas, e é frequentemente usado em pipelines para filtrar a saída de outros comandos.

sed (stream editor) é mais que um filtro: ele pode substituir, deletar, inserir e transformar texto. Sua sintaxe de substituição é s/pattern/replacement/flags. Por padrão, também usa BRE, mas com a opção -E (ou -r em algumas versões) você ativa ERE. O sed é perfeito para operações de edição em arquivos de configuração, relatórios e qualquer transformação textual.

awk é uma linguagem de programação orientada a linhas e campos. Ele usa ERE por padrão em suas expressões de correspondência (~ e !~). O awk é extremamente poderoso para processamento de dados estruturados, como arquivos CSV ou saídas de comandos com colunas, permitindo operações complexas com condicionais e loops.

Vamos ver como cada ferramenta lida com a mesma tarefa: extrair endereços de e-mail de um texto.

# Com grep -E (ERE)
$ grep -E '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b' arquivo.txt

# Com sed -E (substitui por "[EMAIL]") e imprime somente linhas com e-mail
$ sed -E 's/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/[EMAIL]/' arquivo.txt | grep '[EMAIL]'

# Com awk (imprime o primeiro campo que contém @)
$ awk '{for(i=1;i<=NF;i++) if($i ~ /@/) print $i}' arquivo.txt

Perceba que a regex é a mesma, mas a forma de aplicá-la varia. O grep apenas filtra; o sed pode transformar; o awk pode fazer análises mais complexas, como percorrer campos.

Diferenças

Além da sintaxe BRE vs ERE, há diferenças importantes no comportamento e nos recursos entre essas ferramentas, mesmo quando usam ERE.

  • Escopo de atuação: grep opera em linhas completas; sed opera em linhas, mas pode modificar o conteúdo; awk divide cada linha em campos, permitindo operações por campo.
  • Captura de grupos: Todas suportam grupos de captura, mas a forma de referenciá-los difere. No sed, usa-se \1, \2 etc. no padrão de substituição. No awk, usa-se match() e sub() ou gsub() com RSTART e RLENGTH, ou ainda com a função gensub() em algumas versões. No grep, não há substituição, apenas correspondência.
  • Opções de linha de comando: grep oferece -o para imprimir apenas a parte correspondente, -i para ignorar maiúsculas, -v para inverter a seleção. sed tem opções como -n para suprimir a saída padrão e -i para editar arquivo in-place. awk tem variáveis internas como FS (separador de campos) e OFS (separador de saída), além de funções como sub() e gsub().
  • Performance: Para buscas simples em arquivos grandes, grep é geralmente o mais rápido. awk é mais pesado, mas mais flexível. sed é eficiente para edições.

Um exemplo de diferença prática: se você quiser extrair o segundo campo de linhas que começam com "ID:", o awk é muito mais direto que sed ou grep.

# Arquivo: dados.txt
# ID: 123 Nome: João
# ID: 456 Nome: Maria

# awk - imprime o segundo campo (índice 2) das linhas que começam com ID:
$ awk '/^ID:/ {print $2}' dados.txt
123
456

# sed - para obter o mesmo, precisamos capturar em grupo e substituir
$ sed -E 's/^ID: ([0-9]+).*/\1/' dados.txt
123
456

# grep - apenas extrai a parte correspondente (não é ideal para campos)
$ grep -oE '^ID: [0-9]+' dados.txt | grep -oE '[0-9]+$'

Como você pode ver, cada ferramenta tem seu ponto forte. Saber escolher entre elas é uma habilidade importante.

Exemplos

Vamos consolidar com exemplos práticos que combinam regex e as três ferramentas em situações comuns.

Exemplo 1: Extrair IPs de um log

# Log de acesso: linhas como "192.168.1.1 - - [10/Oct/2023:13:55:36] GET /index.html 200"
$ grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log | sort | uniq -c | sort -nr

# Explicação: grep -oE extrai apenas a correspondência, que é o IP. Em seguida, contamos ocorrências.

Exemplo 2: Substituir datas no formato DD/MM/AAAA para AAAA-MM-DD

# Arquivo: relatorio.txt
# Data: 25/12/2023
$ sed -E 's#([0-9]{2})/([0-9]{2})/([0-9]{4})#\3-\2-\1#g' relatorio.txt
Data: 2023-12-25

# Usamos # como delimitador para evitar escapar as barras.

Exemplo 3: Somar valores de uma coluna com awk

# Arquivo: vendas.txt
# Produto,Quantidade,Preço
# Caneta,10,2.50
# Caderno,5,15.00
$ awk -F',' 'NR>1 {total += $2 * $3} END {printf "Total: R$ %.2f\n", total}' vendas.txt
Total: R$ 100.00

# Aqui usamos regex implícita no separador de campos (FS) e expressões aritméticas.

Exemplo 4: Validar formato de e-mail com grep

# Para listar apenas e-mails válidos em um arquivo:
$ grep -E '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' contatos.txt

Exemplo 5: Extrair títulos de tags HTML com sed

# Arquivo: pagina.html
# 

Bem-vindo

Texto

$ sed -E 's/

([^<]+)<\/h1>/Título: \1/' pagina.html Título: Bem-vindo # [^<]+ corresponde a qualquer caractere que não seja '<', capturando o conteúdo.

Esses exemplos mostram o poder da combinação de regex com as ferramentas do shell. Pratique adaptando-os aos seus dados.

Boas práticas e observações finais

Ao trabalhar com regex no shell, algumas boas práticas ajudam a evitar erros e tornar seus scripts mais legíveis:

  • Use aspas simples ao redor de expressões regulares para evitar que o shell expanda metacaracteres como $, * ou !.
  • Prefira ERE (-E) em scripts novos, pois é mais clara e menos sujeita a erros de escape.
  • Teste suas expressões com arquivos pequenos ou usando echo antes de aplicar em dados reais.
  • Documente expressões complexas com comentários, pois regex pode se tornar ilegível rapidamente.
  • Considere a performance: para arquivos enormes, grep é mais rápido que awk; use awk apenas quando precisar de processamento por campos.

Com essas ferramentas e conceitos, você está pronto para manipular texto de forma profissional no shell. Lembre-se de que a prática é fundamental: crie seus próprios desafios e resolva-os usando regex.

Referências

Exercícios

  1. Exercício 1: Usando grep -E, escreva um comando para listar todas as linhas de um arquivo dados.txt que contenham um número de telefone no formato (XX) XXXXX-XXXX, onde X é dígito.
  2. ✓ Resposta:
    grep -E '\([0-9]{2}\) [0-9]{5}-[0-9]{4}' dados.txt
  3. Exercício 2: Com sed -E, substitua em texto.txt todas as ocorrências de "foo" por "bar", mas apenas quando "foo" for uma palavra inteira (não parte de "foobar").
  4. ✓ Resposta:
    sed -E 's/\bfoo\b/bar/g' texto.txt
  5. Exercício 3: Usando awk, imprima a quantidade de linhas de registro.log que contêm a palavra "ERROR" (case-sensitive).
  6. ✓ Resposta:
    awk '/ERROR/ {count++} END {print count}' registro.log
  7. Exercício 4: Extraia todos os endereços IP (IPv4) de um arquivo ips.txt usando grep -oE, mas apenas IPs da faixa 192.168.x.x.
  8. ✓ Resposta:
    grep -oE '192\.168\.[0-9]{1,3}\.[0-9]{1,3}' ips.txt
  9. Exercício 5: Com sed -E, transforme linhas no formato "Nome Sobrenome" para "Sobrenome, Nome" em um arquivo nomes.txt.
  10. ✓ Resposta:
    sed -E 's/^([A-Za-z]+) ([A-Za-z]+)$/\2, \1/' nomes.txt