Probar regex para grep: BRE vs ERE vs PCRE y la trampa de macOS
grep en una caja Linux y grep en macOS no son el mismo programa, e incluso uno de ellos habla tres dialectos de regex según la flag que le pases. Elegir el dialecto correcto te ahorra una tarde cazando escapes.
BRE, ERE, PCRE: tres flavors, un solo binario
grep a secas va por defecto en BRE (Basic Regular Expression). En BRE, los metacaracteres ?, +, {}, (, ) y | son literales. Para que tengan sentido regex hay que escaparlos: \?, \+, \{n\}, \(, \), \|. Es lo inverso a cualquier otro flavor regex de la Tierra.
grep -E (o egrep) cambia a ERE (Extended Regular Expression), donde esos caracteres significan lo que esperas. grep -E 'foo|bar' encuentra cualquiera de las dos palabras; grep 'foo\|bar' hace lo mismo en BRE.
grep -P (solo en GNU) entra en modo PCRE (Perl-Compatible). Eso te da lookahead, lookbehind, cuantificadores perezosos, grupos con nombre y el resto del set moderno.
El grep BSD de macOS no tiene -P
Si escribiste una regex en Linux y a tu compañero en macOS le sale un error de "opción no válida" al pasarle -P, esto es la razón. El grep de macOS es la implementación BSD, que no enlaza con PCRE.
Salidas:
```bash brew install grep # se instala como ggrep ggrep -P 'pattern' file.txt
# O ripgrep, que habla regex de Rust (PCRE-like) brew install ripgrep rg 'pattern' file.txt ```
La mayoría de equipos que corren scripts de shell cross-platform se asientan en ripgrep precisamente porque el dialecto es el mismo en todas partes y el rendimiento es muy superior a grep en árboles grandes. Si eliges herramienta para un proyecto de 2026, elige rg.
Las reglas de escape que de verdad joden
Aún dentro del flavor correcto, hay dos escapes específicos que pillan a la gente a diario:
1. El punto literal. . casa cualquier carácter en todos los flavors. Para casar un punto literal escribes \. En ERE y PCRE es un solo backslash; en BRE también; en tu shell, ese backslash tiene que sobrevivir al quoting. Mete el patrón entero en comilla simple: grep -E '\.com$' urls.txt.
2. La barra vertical literal. | es alternativa en ERE y PCRE, y literal en BRE. Para casar pipe literal en ERE: grep -E '\|' file.txt. Lo mismo en BRE: grep '|' file.txt. Están invertidas; copy-pastear entre scripts te muerde.
Si tienes dudas, usa grep -E (ERE) como default. Es lo más cerca de la regex de JavaScript y Python, y las reglas son predecibles.
Prueba primero la regex en un tester que hable JavaScript
Si construyes el patrón en un tester de regex que habla JavaScript y luego lo portas a grep, dos ajustes cubren la mayoría de casos:
- Sustituye lookarounds ((?=...), (?!...)) por alternativas con anclas o por una llamada a grep -P. No existen en BRE ni ERE.
- Sustituye clases cortas tipo \d por [0-9] para portabilidad BRE/ERE; \d es un perlismo que solo va en PCRE.
Para patrones complejos, prototípalos en un tester visual para ver qué captura cada grupo y luego tradúcelos al dialecto grep que necesites. Iterar solo en CLI va más lento porque grep no te enseña las capturas, solo las líneas que casan.
Ejemplo completo
bash#!/usr/bin/env bash
# BRE: escapa ?, +, |, {}, () para tener sentido regex
grep '^[A-Z][a-z]\{2,\}$' /usr/share/dict/words
# ERE: misma intención, sin escapes
grep -E '^[A-Z][a-z]{2,}$' /usr/share/dict/words
# PCRE (solo GNU): lookahead para "palabra seguida de dígito"
grep -P '\w+(?=\d)' app.log
# Cross-platform: ripgrep habla un solo dialecto en todas partes
rg '^[A-Z][a-z]{2,}$' /usr/share/dict/words ¿Solo necesitas el resultado?
Cuando quieres iterar el patrón visualmente, ver qué captura cada grupo y solo portarlo a grep cuando ya funciona, el tester de regex en navegador te da ese bucle con resaltado en vivo. Flavor JavaScript por defecto, porteas el resultado a grep -E con uno o dos ajustes de escape.
Abrir Tester de Expresiones Regulares (JavaScript) →Preguntas frecuentes
¿Hay forma de que grep se comporte como Perl en macOS sin Homebrew?
La verdad es que no. macOS trae grep BSD sin soporte PCRE. Las opciones prácticas son ggrep vía Homebrew, ripgrep (también Homebrew), o tirar por perl -ne en un pipe. Bajar el patrón a BRE funciona pero te llena todo de escapes.
¿Por qué mi patrón va en egrep pero no en grep?
egrep es grep -E. Tu patrón seguramente usa ?, +, |, {}, o () sin escape, que en BRE son literales. O sigues con -E o escapas cada metacarácter con un backslash. La primera opción es casi siempre la correcta.
¿grep o ripgrep para scripts nuevos?
Ripgrep para cualquier árbol mayor que un archivo. Mismo dialecto regex en cada plataforma, paralelismo por defecto, conoce gitignore. Quédate con grep cuando la portabilidad a un Alpine mínimo o BusyBox sea requisito duro.