Caracteres de control

En un patrón escribimos una cadena de caracteres a buscar en el texto. Pero algunos caracteres no pueden ser incluidos en el patrón, como los saltos de línea o tabulaciones. En su lugar usaremos los siguientes escapes:

EscapeDescripción
\cXBusca un carácter de control (control character) indicado por la letra siguiente, que puede ser una del rango ASCII "A..Z" o "a..z".
\tTabulación horizontal (tab character, TAB, ASCII 9), equivalente a \x09 y \cI.
\nSalto de línea (new line or line feed character, LF, ASCII 10), equivalente a \x0a y \cJ.
\vTabulación vertical (vertical tab character, VT, ASCII 11), equivalente a \x0b y \cK.
\fSalto de página (form feed character, FF, ASCII 12 ), equivalente a \x0c y \cL.
\rRetorno de carro (carriage return character, CR, ASCII 13), equivalente a \x0d y \cM.

Los caracteres de control son los 32 primeros del código ASCII, desde el cero (NULL null char) hasta el 31 (US unit separator) anterior al espacio simple. La gran mayoría de estos se usan para controlar periféricos como impresoras, por lo que poco uso tendrán en las expresiones regulares. Sólo algunos podemos teclearlos y por tanto formarán parte de un texto, como el salto de línea (en Linux) "\n" (LF 10) o la tabulación horizontal "\t" (TAB 9). En Windows el salto de línea son dos caracteres seguidos "\r\n" (CR+LF 13+10), mientras que en Mac es "\r" (CR 13).

Para usar el escape "\cx" tenemos que entender como funciona. Si por ejemplo la "x" es una "j" primero se convierte a maýusculas "J". Esta letra tiene el código ASCII 0100 1010 en binario, 4A en hexadecimal y 74 en decimal. A continuación se invierte el sexto bit, que es lo mismo que restarle 0100 0000 bin, 40 hex o 64 dec, quedando 0000 1010 bin, 0A hex o 10 dec que es el carácter LF, el salto de línea "\n".

Usando "\cA" hasta "\cZ" podemos representar los caracteres de control que van desde el ASCII 1 al 26 en decimal (01 a 1A en hexadecimal). Sin embargo es raro que necesitemos algo más que los habituales saltos de línea, retorno de carro (en Windows) y tabulación horizontal.

Metacaracteres

Un metacaracter es un carácter usado en el patrón para definir un comportamiento. Podemos realizar una búsqueda usando como patrón cualquier carácter, pero algunos tienen un significado especial y deben escaparse con una barra invertida previa si vamos a usarlos literalmente. Por ejemplo, el signo "+" es un metacaracter cuantificador. Si tratásemos de buscar el texto "x+y" tendríamos que escapar en el patrón el signo más /x\+y/. Los metacaracteres los iremos viendo con más detalles en apartados siguientes, por ahora esto es una lista resumen de todos los usados en las expresiones regulares de JavaScript:

MetacaracterDescripción
/Encierran el patrón.
^Ancla la búsqueda al principio del texto cuando el modificador multilínea (m) está desactivado. Si m está activado anclará el principio de línea. Si se usa al inicio de una clase carácter la negará.
$Ancla la búsqueda del final del texto con m desactivado. Si estuviera activado anclaría el final de línea, es decir, los caracteres de salto de línea \n o \r.
*Cuantificador que busca cero o más veces el carácter o expresión precedente.
+Cuantificador que busca una o más veces el carácter o expresión precedente.
?Cuantificador que busca cero o una vez el carácter o expresión precedente. También cuando sigue a un cuantificador lo marca como no codicioso (non-greedy)
.Cuantificador que busca algún caracter excepto el salto de línea.
|Alternativa entre dos subpatrones.
[]Encierra una clase carácter.
-Dentro de un clase carácter cuando está entre dos caracteres define un rango de caracteres. Si está al principio o final de la clase carácter o fuera de ella no tiene ningún efecto.
()Encierra una expresión.
{}Encierra un cuantificador
\Marca el siguiente carácter para que no actúe como metacaracter, que actúe como retroreferencia, como carácter de control, como tipo genérico o para escapar la barra derecha "/" delimitadora de patrón cuando se usa literalmente.

Para usar estos metacaracteres como caracteres, es decir, como literales, hemos de escaparlos con la barra invertida. Aunque dentro de un clase carácter no es necesario escaparlos siempre como veremos en el apartado siguiente.

La expresión regular ignorará un escape con barra invertida en cualquier parte del patrón si un carácter no lo necesita o no se le puede aplicar escape. Por ejemplo, es lo mismo /\a/g que /a/g porque no está especificado que "\a" tenga un comportamiento especial. No hay que olvidar que la barra invertida hace funciones de escape de metacaracter, de caracteres de control, retroreferencias y tipos genéricos. Por lo tanto conviene saber cuando usar el carácter barra invertida, pues tenemos que conocer exactamente que hace cada carácter en un patrón.

Escapado de caracteres en el String del constructor RegExp

En el apartado anterior vimos los metacaracteres que necesitan ser escapados en un patrón de expresión regular. Por ejemplo un patrón como /.\./g buscaría coincidencias con cualquier carácter seguido de un punto. El primer punto es un metacaracter, el segundo es literalmente un punto.

Con una declaración como var reg = /.\./g; instanciaríamos ese patrón. Pero si usamos el constructor de expresiones regulares tendríamos que escapar dos veces las apariciones de la barra invertida en el String del patrón: var reg = new RegExp(".\\.", "g");. Esto es debido a que la barra invertida también sirve de escape de caracteres en un String. Así el String ".\." se convierte en ".." porque un String no espera un escape para el punto. Sin embargo ".\\." se convierte en ".\." pues el String si espera encontrar una barra invertida escapada, en cuyo caso la tomará como literal. En el siguiente ejemplo podrá comprobar el resultado de ambas posibilidades.

Ejemplo: Escapado doble con constructor RegExp

Usar el siguiente string
Texto sobre el que buscar cualquier carácter al que le siga un punto:
102.56 + 34.97
Patrón del constructor var reg = new RegExp(str, "g");   
Resultado de la búsqueda var bus = texto.match(reg);
 
El resultado correcto debería ser "2.", "4.".

Clases carácter

Una clase carácter es un conjunto de caracteres dentro de los corchetes "[]".

PatrónDescripción
[abc]Busca caracteres que coincidan con alguno de los de la clase incluidos en los corchetes. Con /[abc]/ buscamos alguno de los caracteres "a, b" o "c".
[^abc]El caracter ^ al inicio de la clase niega su resultado. Es decir, en este caso buscaría caracteres que no sean los de la clase.
[a-z]Dos caracteres seguidos separados por un guión dentro de una clase representan un rango de caracteres. Así /[a-cn2-5]/ buscaría las letras "a, b, c", el caracter "n" y finalmente alguno de "2,3,4,5".

Dentro de una clase carácter no es necesario escapar la mayor parte de metacaracteres, a excepción del propio corchete "]" que cierra la clase, la barra invertida "\" así como "^" y "-" en algunos casos como veremos ahora.

El carácter "^" actúa como metacaracter sólo si está al principio de la clase. Si aún es necesario ubicarlo ahí de forma literal lo escaparíamos con la barra invertida. Por ejemplo /[\^-`]/ busca en el rango de caracteres "^_`" (códigos ASCII del rango 94 a 96). En cualquier otra posición que no sea la inicial tiene significado literal. Por ejemplo /[^^-`]/ buscaría cualquier caracter excepto los tres del rango "^-`", pues el primer "^" niega la clase y el segundo "^" actúa como literal en el rango "^-`".

El otro metacaracter a considerar es el guion intermedio "-". Al inicio y final de clase no es necesario escaparlos. Sólo tiene significado cuando está entre dos caracteres siempre que el primer caracter de la clase y del rango no sea "^".

Cuantificadores

Los cuantificadores determinan el número de repeticiones que deben producirse para encontrar la coincidencia.

PatrónDescripción
*Busca el carácter o subexpresión precedente cero o más veces.
+Busca una o más veces.
?Busca cero o una vez.
{n}Busca exactamente n veces siendo n un entero no negativo.
{n,}Busca al menos n veces. Veáse que {0,} es igual que * y {1,} igual que +.
{n,m}Busca como mínimo n veces y como máximo m veces. Veáse que {0,1} es igual que ?.

Los cuantificadores son por defecto codiciosos o ambiciosos (greedy). Esto significa que probarán todas las posibilidades hasta que falle. Para hacerlos no codiciosos (non-greedy o lazy) agregamos un "?" a continuación. Por ejemplo, si el texto donde buscar fuera un trozo de código como el siguiente tratando de buscar los elementos con sus atributos, es decir, los cabeceras de tags:

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div" id="un-div">DIV</div>

La búsqueda debería encontrar las cuatro cabeceras (recuerde que cada color alternativo rojo y azul representa una secuencia seguida de coincidencias, separando por colores para poder diferenciarlas):

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div" id="un-div">DIV</div>

Si usamos el patrón /<[a-z]+(.*)>/g no resaltará adecuadamente las cabeceras:

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div" id="un-div">DIV</div>

En cambio si hacemos el cuantificador no ambicioso /<[a-z]+(.*?)>/g el resultado es el correcto. Veáse por ejemplo que al primer caracter ">" en <strong> finaliza la búsqueda si no es codicioso. En otro caso seguiría buscando hasta que otra condición haga fallar el patrón. Esa condición sucede en el carácter ">" de </em> inmediatamente antes de un salto de línea, pues el punto busca cualquier carácter hasta encontrar un salto de línea, como veremos en un ejemplo similar en el siguiente apartado.

Tipos genéricos

Hay algunos caracteres que tienen un significado especial como tipo genérico. Se trata de una facilidad para escribir patrones más simples agrupando en tipos como dígitos, palabras, espacios blancos y cualquier carácter.

PatrónDescripciónEquivale a
.Busca cualquier carácter excepto "\n" o "\r".
\sBusca un carácter espacio blanco (space character)[ \f\n\r\t\v]
\SBusca un carácter que no sea un espacio blanco[^ \f\n\r\t\v]
\dBusca un dígito (digit character)[0-9]
\DBusca un carácter que no sea un dígito[^0-9]
\wBusca un carácter de palabra (word character), incluyendo guión bajo "_"[A-Za-z0-9_]
\WBusca algo que no sea un carácter de palabra[^A-Za-z0-9_]

Los tipos genéricos también pueden incluirse en una clase carácter. Así algo como /[a-z\d]/ agrega la clase carácter "\d" a la clase exterior, siendo lo mismo que /[a-z0-9]/.

El tipo genérico "\w" es equivalente a "[A-Za-z0-9_]", no encontrando ciertos caracteres particulares de otros idiomas que no sea el inglés. Para usarlo en español tenemos que ampliar esa selección con "[\wá-úÁ-ÚüÜñÑçÇ]".

El punto "." merece una atención especial. En principio busca cualquier carácter excepto los saltos de línea. Para buscar cualquier carácter incluyendo los saltos de línea podemos usar /[\s\S]/, puesto que con "\s" buscamos espacios blancos y con "\S" el resto. El punto no tiene ningún significado especial dentro de una clase carácter, por lo que no es necesario escaparlo dentro de la clase. Por lo tanto para buscar un punto literal el patrón /\./ es equivalente a /[.]/.

A veces tendemos a usar el punto en un patrón como /.*?/g olvidando que la búsqueda finaliza cuando encuentra un salto de línea. Tomemos el mismo ejemplo de código del apartado anterior donde buscábamos cabeceras de tags:

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div"
id="un-div">DIV</div>

Ahora hay una diferencia, hay un salto de línea dentro de la cabecera del DIV. La búsqueda debería encontrar los siguientes (recuerde que cada color alternativo rojo y azul representa una coincidencia):

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div"
id="un-div">DIV</div>

Si usamos el patrón /<[a-z]+(.*?)>/g no encontrará el DIV:

<strong>STRONG</strong><em>EM</em>
<span class="mi-span">SPAN</span>
<div class="mi-div"
id="un-div">DIV</div>

Lo que está sucediendo es que el patrón buscará todas las posibilidades hasta que falle. Y fallará cuando encuentre un salto de línea. Para evitar esto tenemos dos opciones, o convertimos todos los saltos de línea del texto en otra cosa (p.e. un espacio normal) antes de aplicar el patrón o en cambio usamos un patrón ajustado a la búsqueda como /<[a-z]+([^>]*)>/g. En este caso el patrón fallará al encontrar el carácter siguiente al primer ">" que cierra el tag de cabecera, devolviendo el resultado correcto.

Escape de códigos de caracteres

Podemos escribir los caracteres a buscar directamente en el patrón o usar sus codificaciones Unicode para representarlos.

PatrónDescripción
\xh1h2Busca un carácter con el código hexadecimal h1h2 (dos dígitos hexadecimales). Por ejemplo, /\x61/g encuentra todas las letras "a" minúsculas. Los códigos de caracteres son los de Unicode hasta el 255 (ff en hexadecimal).
\uh1h2h3h4Busca un carácter UNICODE con el código hexadecimal h1h2h3h4 (cuatro dígitos hexadecimales). Por ejemplo, para buscar la letra griega PI "π" que tiene un valor Unicode 03c0 haremos /\u03c0/g.
\n1n2n3Busca un carácter con el código octal n1n2n3, con uno a tres dígitos octales 0..7. Por lo tanto cubren los caracteres de Unicode en el rango 000...377 en octal, que es lo mismo que 0..255 en decimal y 00..ff en hexadecimal. Por ejemplo, para buscar la letra "a" minúscula usaríamos el patrón /\141/g.

Vea que los escapes hexadecimales empiezan con "\x" o "\u". Pero no los de octal. Y esto hay que tenerlo en cuenta porque algo como \251 puede buscar el caracter "©" (código octal es 251) o bien puede referirse a una retroreferencia. Cuando se encuentra en el patrón "\n" siendo "n" un número entero mayor que cero, JavaScript intentará primero buscar una retroreferencia, es decir, un grupo de captura con ese número de orden. Si no lo encuentra y pudiera ser un número octal lo convertirá en su correspondiente carácter.

Aunque los grupos de captura y las retroreferencias las veremos en apartados siguientes, conviene exponer un ejemplo para aclarar esto. Supongamos que tenemos que buscar una cadena de texto como "abc1234abc00" con una estructura de letras seguidas por dígitos, repetiéndose otra vez las letras del inicio y finalizando con el literal "00", podríamos usar erróneamente un patrón como el siguiente

/([a-z]+)\d+\100/g

Aquí el \1 intenta ser una retroreferencia al primer grupo de captura ([a-z]+), las letras del inicio, y luego le sigue el literal "00". Pero esto encontraría algo como "abc1234@" pero no lo que estamos buscando "abc1234abc00", dado que "\100" busca en octal el caracter "@". Para separar la retroreferencia del número "00" podemos usar la grupos de no captura e incluso las clases de carácter. Alguno de estos patrones podría servirnos;

/([a-z]+)\d+\1[0][0]/g
/([a-z]+)\d+(?:\1)00/g
/([a-z]+)\d+\1(?:00)/g

Anclas

Las anclas nos pemiten fijar un patrón a al principio o final de línea o texto y también al inicio y final de palabra.

PatrónDescripción
^Fija o ancla la posición de búsqueda al inicio del texto cuando el modificador multilínea está desactivado. Si estuviera activado esta fijación sería con respecto al siguiente caracter de un salto de línea "\n" o "\r", anclando al inicio de cada línea.
$Fija o ancla la posición de búsqueda al final del texto cuando el modificador multilínea está desactivado. Si estuviera activado esta fijación sería con respecto al anterior caracter de un salto de línea "\n" o "\r", anclando al final de cada línea.
\bFija o ancla la posición de búsqueda entre una palabra y un espacio. Podemos decir que encuentra una posición que sea límite de palabra (word boundary). Por ejemplo, /\ba/g encontraría todas las letras "a" que sean iniciales de palabra.
\BEncuentra una posición que no sea límite de palabra. Con /\Ba/g buscaríamos todas las letras "a" que no sean iniciales de palabra.

El ancla "\b" es un límite de palabra teniendo en cuenta que un caracter de palabra es "\w". Por lo tanto "\b" es en parte equivalente a "\W" (W mayúscula) que busca un carácter que no sea de palabra, es decir, "[^A-Za-z0-9_]". La diferencia es que un ancla no consume caracteres mientras que "\W" si lo hará, como explicaremos más abajo. Por otro lado con "\b" no se incluyen los caracteres específicos de otros idiomas.

Por lo tanto un patrón como /\ba/g busca la letra "a" inicial y también todas aquellas que sigan a un letra con tilde o una eñe. Así que para usar un equivalente a "\b" en español tendría que usar "[^\wá-úÁ-ÚüÜñÑçÇ]" que es la adaptación al español del tipo "\W". El patrón /\ba/g para texto en español debería convertirse en /[^\wá-úÁ-ÚüÜñÑçÇ][aá]/g, agregando la "á" con tilde para que la encuentre también. Podemos resumir diciendo que hay que entender las anclas de límite de palabra para poderlas aplicar correctamente en otro idioma que no sea el inglés.

Las anclas no consumen caracteres, es decir, no devuelven lo que encuentran. Así /\b[aá]/g encuentra las "a" que sean inicio de palabra, devolviendo sólo la letra sin el limitador previo. Si nos construimos un limitador como el anterior /[^\wá-úÁ-ÚüÜñÑçÇ][aá]/g debemos tener en cuenta que ese carácter previo también será devuelto. Podemos recuperar exactamente lo que buscamos usando un grupo de captura como veremos en el siguiente apartado.

Grupos de captura, alternativas y retroreferencias

Un grupo es una parte de un patrón entre paréntesis, denominado a veces como subpatrón. Pueden haber grupos de captura y grupos de no captura que veremos en un siguiente apartado. Una de las finalidades de ambos grupos es precisamente agrupar alternativas. Sólo los de captura almacenan las coincidencias encontradas, pudiendo usar retroreferencias para referirnos a ellas.

PatrónDescripción
()Un grupo de captura es un parte del patrón entre paréntesis que no empice con "?:", "?=" o "?!" (estos serían de no captura).
|Define distintas alternativas de búsqueda.
\nSiendo "n" un número entero mayor que cero, esto constituye una retroreferencia para referirnos a un grupo de captura almacenado anteriormente en el patrón.

En el apartado anterior sobre las anclas construíamos una a medida del español que sirviera como limitador de palabra. Con /[^\wá-úÁ-ÚüÜñÑçÇ][aá]/g encontrábamos todas las letras "a" que fueran inicio de una palabra. Pero recuperábamos también el limitador. Con /[^\wá-úÁ-ÚüÜñÑçÇ]([aá])/g hacemos un grupo de captura "([aá])" de tal forma que podemos extraerlo con la ejecución de la búsqueda. Aunque en un tema posterior entraremos en más detalle, por ahora baste decir que tendríamos acceso a la coincidencia completa con el patrón en la propiedad $& o lastMatch del objeto RegExp. Mientras que el primer y único grupo de captura de este ejemplo lo tendríamos en la propiedad $1.

Las alternativas pueden ir a nivel de patrón exterior, algo como /vaca|carnero/g buscaría las palabras "vaca" o "carnero" en el texto. La alternativa es asimilable a una disyunción lógica. Busca una cosa u otra. ¿Y cómo hacer una conjunción?, es decir, buscar una cosa y otra. En el tema que habla sobre un buscador con PHP me enfrenté con este problema para buscar en un índice de palabras alguna de una lista, todas las de la lista en su orden y todas las de la lista en cualquier orden. En el ejemplo interactivo pondríamos algo como:

/rocín.*?galgo|vaca.*?carnero/g

para buscar "rocín" y "galgo" en una misma línea y "vaca" y "carnero" en otra línea. Si las palabras pudieran aparecer en distinto orden habría que expresar todas las permutaciones posibles:

/rocín.*?galgo|galgo.*?rocín|vaca.*?carnero|carnero.*?vaca/g

Veámos ahora que son las retroreferencias aplicándolo a un ejemplo. Supongamos que tenemos un trozo de código HTML y tratamos de buscar elementos con etiqueta o tag de apertura y cierre y con contenido de sólo texto. Si en el ejemplo interactivo del tema anterior probamos este patrón:

/<(\w+)[^>]*>[^<]+<\/\1>/g

Y lo aplicamos a este trozo de código lo obtendremos resaltado así:

<body>
    <h1>Título</h1>
    <img src="xxx" />
    <p>Esto es un párrafo
    con varias líneas de texto.</p>
</body>

El resalte se hace con JavaScript posteriormente, resaltando en rojo las coincidencias pares y azules las impares. Cada coincidencia completa con el patrón a su vez tiene un conjunto de resultados de los grupos de captura que son los paréntesis del patrón. Se enumeran desde 1, desde la iquierda a derecha y desde fuera hacia dentro. Así "(\w+)" nos almacenará el primer resultado. Para buscar el tag de cierre utilizamos la retroreferencias "\1" para referirnos a este grupo de captura.

Con el anterior patrón encontrábamos todos los elementos HTML con sólo texto en el interior. Pero no los elementos vacíos como el IMG. Podemos usar una alternativa para encontrarlos:

/<(\w+)[^>]*(>[^<]+<\/\1>|\/>)/g

Dando como resultado que ahora si encuentre el elemento IMG:

<body>
    <h1>Título</h1>
    <img src="xxx" />
    <p>Esto es un párrafo
    con varias líneas de texto.</p>
</body>

Leer un patrón a veces puede ser complicado. Es conveniente dividirlos para entender cada porción:

  • <(\w+)[^>]*: Primera porción busca un tag de apertura y posibles atributos.
  • (>[^<]+<\/\1>|\/>): La segunda porción es un grupo de 2 alternativas.
    • >[^<]+<\/\1>: Una alternativa cierra el tag de apertura, texto y finalmente con la retroreferencia busca el tag de cierre.
    • \/>: La segunda alternativa es un tag de cierre de elementos vacíos.

Grupos de no captura

Los grupos de captura almacenan su coincidencia encontrada y esto supone un consumo de recursos a veces innecesario. Sólo hará falta almacenarlas cuando nos vamos a referir a ellas desde una retroreferencia o bien son parte de la salida de resultados. En otro caso podemos usar los grupos de no captura. Otros grupos que se expresan entre paréntesis son los de búsqueda hacia adelante.

PatrónDescripción
(?:)Este grupo no captura la coincidencia encontrada, siendo ideal para expresar alternativas como 4567(?:82|91), que es una expresión más simple que 456782|456791.
(?=)Es un grupo de no captura de búsqueda hacia adelante positiva (positive lookahead) que no consume caracteres.
(?!)Es un grupo de no captura de búsqueda hacia adelante negativa (negative lookahead) que no consume caracteres.

Probaremos estos grupos en el ejemplo interactivo con el texto del primer párrafo del Quijote que reproducimos aquí para mayor comodidad:

En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que
vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor.
Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los
sábados, lantejas los viernes, algún palomino de añadidura los domingos, consumían las tres
partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las
fiestas, con sus pantuflos de lo mesmo, y los días de entresemana se honraba con su vellorí
de lo más fino.

Queremos buscar palabras de una o dos letras, y opcionalmente algunas de tres que terminen en "s". La primera intención será usar "\w" para buscar cualquier carácter de palabra con una longitud "{1,2}", luego una "s?" que aparezca cero o una vez. Todo ello con los limitadores de palabra "\b":

/\b\w{1,2}s?\b/g

Esto en principio funciona, pero no encuentra la palabra "más" pues tiene tilde. Necesitamos un patrón para el idioma español tal como explicamos en un tema anterior:

/\b[\wá-úÁ-ÚüÜñÑçÇ]{1,2}s?\b/g

Ahora econtrará palabras con tilde. Pero tenemos otro problema pues encuentra erróneamente "ía" en la palabra "vivía". Esto es porque el limitador de palabra "\b" sólo funciona bien con el idioma inglés. Construyamos nosotros nuestro propio limitador de palabra. Para hacerlo sencillo definimos que una palabra estará precedida siempre por un espacio blanco o ser la primera del texto, o de la línea si estamos buscando con el modificador multilínea activado. Por otro lado a una palabra siempre le seguirá un espacio blanco, un punto, una coma o un punto y coma. O ser la última palabra del texto o de la línea. Convertimos esto en un patrón:

/(?:^|\s)([\wá-úÁ-ÚüÜñÑçÇ]{1,2}s?)(?:$|[\s.,;])/g

Hemos incluido un grupo de captura "([\wá-úÁ-ÚüÜñÑçÇ]{1,2}s?)" para recuperar la palabra sin los limitadores. Porque las anclas "\b" no forman parte de lo recuperado, pero ahora con nuestros limitadores el patrón total si incluirá los espacios blancos y los signos de puntuación. En nuestro proceso tras la búsqueda sólo tendremos que acceder al primer grupo de captura para obtener las coincidencias con las palabras que estamos buscando.

Seguimos analizando ese patrón. El inicio "(?:^|\s)" es un grupo de no captura para incluir las alternativas de ancla de inicio de texto o un espacio blanco. Esto nos permite encontrar la primera palabra "En" en el texto, pues le sigue un espacio blanco, tal como se declara en el grupo de no captura del final del patrón "(?:$|[\s.,;])".

Sin embargo no encuentra la siguiente palabra "un" porque ya hemos consumido el carácter del espacio final en la coincidencia anterior, carácter que ahora es el espacio inicial para la siguiente coincidencia. Para evitar esto podemos usar el grupo de no captura de búsqueda hacia adelante positiva:

/(?:^|\s)([\wá-úÁ-ÚüÜñÑçÇ]{1,2}s?)(?=$|[\s.,;])/g

El grupo final "(?=$|[\s.,;])" se encarga de mirar hacia adelante buscando un espacio blanco, un signo de puntuación o final de texto. Si es así devolverá la coincidencia del patrón excluyendo este grupo de no captura. Además no consumirá caracteres para buscar la siguiente coincidencia, pues ésta se empezará a buscar en la posición donde finaliza el patrón antes de este grupo de no captura.