wandres.dev
PATRONES · no son expresiones regulares

Las cuatro funciones que consumen patrones

Localizar con find, extraer con match, iterar con gmatch y sustituir con gsub en sus tres formas: cadena de reemplazo, tabla de consulta y función de transformación. Qué devuelve exactamente cada una y qué errores nacen de ignorarlo.

⏱ 20 min

El lenguaje de patrones no se ejecuta solo: lo consumen cuatro funciones de la biblioteca de cadenas, y cada una impone su propio contrato de entrada y de salida. Dominar los patrones sin dominar ese contrato produce código que funciona por accidente: bucles que se cuelgan porque una coincidencia vacía no avanza, funciones que devuelven dos valores cuando quien llama esperaba uno, sustituciones que se saltan la mitad de las apariciones porque el patrón llevaba un anclaje. Este capítulo recorre las cuatro funciones y, sobre todo, la tabla de lo que devuelve cada una, que es donde se esconde la mayoría de los fallos reales.

🎯 Al terminar esta lección sabrás
  • Elegir entre localizar, extraer, iterar y sustituir según lo que necesites del texto.
  • Recitar los valores de retorno de las cuatro funciones y sus casos de fallo.
  • Usar las tres formas de reemplazo de la sustitución y saber qué significa devolver un valor falso.
  • Evitar las trampas de la coincidencia vacía, del anclaje y del segundo valor devuelto.

Localizar y extraer

string.find responde dónde está: devuelve la posición inicial y la final de la coincidencia y, si el patrón lleva capturas, las añade detrás. Si no hay coincidencia devuelve un valor nulo. Acepta una posición inicial, que puede ser negativa para contar desde el final, y un cuarto argumento que desactiva por completo el motor de patrones y hace una búsqueda literal de subcadena.

string.match responde qué hay: devuelve las capturas, o la coincidencia entera si el patrón no tiene ninguna, y un valor nulo si falla. También acepta posición inicial. Es la función que usarás el noventa por ciento de las veces.

local s = "error 404 en /api/usuarios"

print(s:find("%d+"))                 --> 7   9
print(s:find("%d+", 10))             --> nil
print(s:find("(%d)(%d%d)"))          --> 7   9   4   04
print(s:find("/api", 1, true))       --> 14  17   (busqueda literal, sin motor)
print(s:match("%d+"))                --> 404
print(s:match("en (%S+)"))           --> /api/usuarios

La diferencia entre las dos es la razón por la que existen ambas. find es la elección cuando vas a seguir trabajando sobre la cadena original y necesitas desplazamientos para recortar o para continuar la búsqueda; match lo es cuando solo quieres el dato. Usar find y descartar las posiciones es una señal segura de que querías match; usar match y después buscar de nuevo la posición es una doble pasada evitable.

💡
La búsqueda literal salta el motor entero

El cuarto argumento de find no es una comodidad para no escapar: es un camino de código distinto, que compara bytes sin interpretar nada. Cuando buscas una cadena fija procedente de datos externos es más rápido y, sobre todo, inmune a los caracteres mágicos que esa cadena pudiera contener.

Iterar

string.gmatch devuelve un iterador: una función que en cada llamada entrega la siguiente coincidencia, con la misma regla de las capturas que match, y un valor nulo cuando se agotan. Está pensada para el bucle genérico y es la forma idiomática de tokenizar.

local csv = "ada,grace,alan,edsger"
for nombre in csv:gmatch("[^,]+") do io.write(nombre, " ") end

for clave, valor in ("a=1, b=2, c=3"):gmatch("(%w+)=(%w+)") do
  print(clave, valor)
end

-- contar lineas sin construirlas
local n = 0
for _ in ("uno\ndos\ntres"):gmatch("[^\n]+") do n = n + 1 end
print(n)                              --> 3

Dos particularidades gobiernan su comportamiento. La primera: un acento circunflejo al comienzo del patrón no funciona como anclaje en esta función, porque anclar al inicio impediría toda iteración más allá de la primera coincidencia. La segunda: si el patrón puede encajar con la cadena vacía, el iterador avanza una posición tras cada coincidencia vacía para no quedarse clavado, lo que produce una coincidencia vacía por cada byte del sujeto y suele ser señal de un cuantificador mal elegido.

El iterador que devuelve mantiene su propio estado interno —la posición por la que va— y por tanto es de un solo uso: no se rebobina, no se reinicia y no debe compartirse entre dos bucles. Crear uno nuevo cuesta muy poco, así que la solución a cualquier necesidad de repetir el recorrido es llamar otra vez a la función. Y como el patrón se aplica sobre la cadena original en cada vuelta, modificar esa cadena dentro del bucle no afecta al recorrido: en Lua las cadenas son inmutables y lo que iteras es siempre la que se pasó al empezar.

-- el idioma para partir por un separador, tolerando campos vacios
-- el separador debe llegar ya escapado si contiene caracteres magicos
local function partir(s, sep)
  local partes = {}
  for campo in (s .. sep):gmatch("(.-)" .. sep) do
    partes[#partes + 1] = campo
  end
  return partes
end
print(#partir("a,b,,c", ","))          --> 4
flowchart TB
A[Que necesitas del texto] --> B[Saber si aparece y donde]
A --> C[Quedarte con un dato]
A --> D[Recorrer todas las apariciones]
A --> E[Producir una cadena nueva]
B --> F[Usa find y quedate con las posiciones]
C --> G[Usa match y quedate con las capturas]
D --> H[Usa gmatch en un bucle generico]
E --> I[Usa gsub y recoge tambien el contador]

Sustituir

string.gsub es la función más rica de la biblioteca de cadenas. Recibe el sujeto, el patrón, un reemplazo y un límite opcional de sustituciones, y devuelve dos valores: la cadena resultante y el número de sustituciones efectuadas. El reemplazo admite tres formas distintas.

Con una cadena, el texto se copia tal cual salvo por las secuencias de porcentaje: un porcentaje seguido de un dígito del uno al nueve inserta la captura correspondiente, el cero inserta la coincidencia entera, y el doble porcentaje inserta el símbolo literal. Cualquier otra combinación es un error.

Con una tabla, se consulta la tabla usando como clave la primera captura, o la coincidencia entera si no hay capturas.

Con una función, se la llama con todas las capturas como argumentos y se usa lo que devuelva.

print(("2026-08-04"):gsub("(%d+)-(%d+)-(%d+)", "%3/%2/%1"))
--> 04/08/2026     1

local vars = { nombre = "Ada", ano = "1815" }
print(("Hola $nombre, naciste en $ano"):gsub("%$(%w+)", vars))
--> Hola Ada, naciste en 1815     2

print(("suma 2 y 40"):gsub("%d+", function(n) return tonumber(n) * 2 end))
--> suma 4 y 80     2

local texto, cuantas = ("a.b.c"):gsub("%.", "-")
print(texto, cuantas)             --> a-b-c   2

En las formas de tabla y de función hay una regla que lo cambia todo: si el valor obtenido es nulo o falso, no se sustituye nada y la coincidencia se conserva intacta. Esa convención convierte la sustitución en un filtro condicional sin necesidad de una segunda pasada: la función decide caso por caso si transforma o deja pasar, y para dejar pasar basta con no devolver nada. Si el valor devuelto no es ni cadena ni número ni falso, en cambio, se produce un error de valor de reemplazo no válido.

🔢

El contador cuenta coincidencias

Si el reemplazo conserva el original, el segundo valor sigue contando la coincidencia. Sustituir por la cadena vacía y quedarse con el contador es el idioma para contar apariciones.

⚠️

Dos valores viajan lejos

Escribir una función cuyo cuerpo devuelve directamente una sustitución propaga los dos valores a quien llama. Rodéala de paréntesis para truncar a uno.

🔍

El anclaje limita a una

Un patrón anclado al inicio solo puede encajar en la posición uno, así que la sustitución se aplica como mucho una vez por muy grande que sea el límite.

🧩

El límite es un tope, no un objetivo

El cuarto argumento detiene la sustitución tras ese número de coincidencias. Es la manera de reemplazar solo la primera aparición.

Función Devuelve al encajar Devuelve al fallar
find inicio, fin y las capturas un valor nulo
match las capturas o la coincidencia entera un valor nulo
gmatch un iterador que entrega lo mismo que match el iterador entrega nulo y el bucle acaba
gsub la cadena nueva y el número de sustituciones la cadena original y el número cero
La sustitución es una función de orden superior disfrazada

Mira otra vez las tres formas de reemplazo y compáralas con algo que ya conoces. Una cadena con marcadores es una plantilla; una tabla es una consulta; una función es un cálculo. Esa misma tricotomía aparece en otro punto central del lenguaje: el metamétodo de indexación acepta una tabla o una función, y el iterador genérico acepta cualquier cosa que se pueda llamar. No es coincidencia: es el principio de que en Lua una correspondencia entre entradas y salidas se expresa indistintamente como dato o como código, porque ambos son valores de primera clase y el consumidor no necesita saber cuál recibió. La sustitución no tiene tres modos de operación, tiene uno solo con tres notaciones para la misma idea de aplicación, y por eso puedes empezar con una tabla, descubrir que necesitas un caso especial y sustituirla por una función sin tocar nada más. Añade a eso la convención del valor falso, que en apariencia es un detalle de conveniencia y en realidad completa el álgebra: sin ella, la función de reemplazo sería una transformación total y estarías obligado a devolver la coincidencia original para dejarla como estaba, con el riesgo de devolverla mal escapada; con ella, la función es una transformación parcial y la sustitución se convierte a la vez en filtro y en mapa sobre el conjunto de coincidencias. El segundo valor devuelto cierra el círculo y añade un pliegue: cuentas mientras transformas. Lo que parecía una función de reemplazo de texto es, leída con atención, un recorrido de orden superior sobre todas las apariciones de un patrón, con acumulador incluido, escrito en una línea. Casi todo lo que se hace en Lua con texto pasa por ahí.

⚔️ Ejercita el contrato de cada función
  1. Escribe la misma extracción de un número con find, con match y con gmatch, y razona cuál sobra en cada contexto.
  2. Cuenta las apariciones de una palabra usando el segundo valor de la sustitución y compáralo con un bucle explícito.
  3. Escribe un expansor de plantillas con tabla que deje intactos los marcadores no definidos. Después pásalo a función y añade un valor por defecto.
  4. Provoca el bucle de coincidencias vacías con un cuantificador de cero o más en una iteración y explica por qué el iterador no se cuelga.
  5. Escribe una función que normalice espacios y devuelva la cadena. Compruébala pasando su resultado a otra función que reciba dos parámetros y explica lo que ocurre.