Sobre expresiones regulares

Tus preguntas. Algoritmos o Grupos de Comandos formando Programas Escripts.
Responder
Jonny
Profesional del Autoit
Mensajes: 1042
Registrado: 30 Jun 2008, 20:08

Sobre expresiones regulares

Mensaje por Jonny »

Bueno, pues desde no hace mucho, me puse con las ER enserio, y al final, vatallando con la documentación en español, he conseguido entenderlas y lo mejor, usarlas :).

Pero hay todavía algo que: o bien no entiendo de la documentación, o no se usar.
(?: ... ) Grupo no-captador. Se comporta simplemente como un grupo normal, pero no guarda los caracteres coincidentes en el arreglo tampoco puede el texto coincidido será usado para volver a referenciar.
Bueno. Suelo leer la documentación en español, porque más que la inglesa, desde luego, la entiendo. Aunque, muchas veces pienso que seguro que entendería mejor la inglesa... :P.

Imagino que eso que he copiado, biene a decir, que un grupo no-captador, no guarda en el array de coincidencias lo que coincida en la cadena revisada, con lo que este contenga; y bueno, lo demás, supongo que quiere decir algo así, como que no puede usarse la coincidencia en stringregexpreplace, como referencia (con $1, etc).
Vaya, que entiendo que los grupos no-captadores, coinciden con el patrón dado, pero descartan lo que contengan.

Si es como digo, debe ser que no se usar esos grupos, porque por ejemplo, hago algo como esto:

Código: Seleccionar todo

(?:\x26)+[[:digit:]]+
Se supone, que la ER anterior, debería devolver sólo los dígitos que haya después de uno o más & ¿verdad?. Pues no me funciona :).
¿Por qué?. Eso es lo que no se ... :).

A ver si alguien que domine más las ER sabe explicarme eso de los grupos no captadores, y como usarlos.

Y ya puestos con las ER, a ver si alguien sabe explicarme eso de las referencias en StringRegExpReplace(), porque si intento usarlas para sustituir una coincidencia encontrada poniendo una referencia dentro de una función (anidada en StringRegExpReplace() como sustitución) no me funciona, sólo si la uso directamente.

Salu2!
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

Sí, aunque la traducción es más bien mala, lo has entendido bastante bien.
Un grupo no capturable no se guarda en los resultados (cuando se usa un valor mayor de 0 en el tercer parámetro de StringRegExp, por ejemplo) y como bien dices como esas referencias no se guardan no se pueden usar tampoco en la propia función regular o para formar la cadena de reemplazo en StringRegExpReplace.

Sí, las referencias también se pueden usar dentro de la propia ER. Por ejemplo la ER
(a|b),\1
empareja con "a,a" y "b,b", pero no con "a,b" por ejemplo, pues la referencia \1 obliga a que se reutilice lo que se ha capturado en el primer (y en este caso único) grupo.

Respecto a tu ER no va por dos cosas:
La principal es que cómo va a devolverte los dígitos si no los capturas en un grupo, además como lo quieres capturar, ese grupo debe ser capturable. Ese es el segundo error, ¿para qué usas un grupo no capturable con el \x26 si no usas ese grupo para nada? Para eso ahorratelo y simplificas la ER.
Así que más correctamente sería:
\x26+([[:digit:]]+)
Es decir, el grupo estaba donde no tocaba y además no era del tipo correcto :smt005

Además no entiendo por qué usas la codificación ASCII hexadecimal, cuando ese carácter es directamente imprimible, yo lo pondría directamente y de paso queda más claro:
&+([[:digit:]]+)

Por cierto, el conjunto [:digit:] es equivalente a \d, como el conjunto sólo usa eso yo lo simplificaría más:
&+(\d+)

No se parece mucho a la que tenías ¿eh?, ja, ja. Bueno, espero que hayas entendido mejor lo de los grupos: no había que usar el no capturable en lo que no quieres, sino el capturable en lo que quieres.
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

Ah, respecto a la segunda cuestión, a StringRegExReplace no se le puede pasar una función para que la propia ER la aplique.
Piensa que la función recibe un string, hagas lo que hagas.
Así por ejemplo si quiero sustituir las a por sus mayúsculas (por ejemplo pasarle "casa" y que devuelva "cAsA") se haría:
StringRegexpReplace("casa", "(a)", "A")

Pero si quiero una función, por ejemplo para que pase tanto las a como las c (y que devuelva "CAsA") puedo intentar:
StringRegexpReplace("casa", "(a|c)", StringUpper("\1"))

¡Pero eso no funciona! Eso no me pasa a mayúscula el grupo capturado (la a o la c) ¡porque se ejecuta antes de ejecutar la función!
Los parámetros se resuelven antes.
Esto quiere decir que en la llamada se evalúa el parámetro, StringUpper("\1") pasa "\1" a mayúsculas, que como no tiene letras se queda igual, así que StringRegExReplace recibe la cadena "\1" que es dejar el grupo como se captura.

¿Se entiende por qué no funciona?

Hay una solución no muy eficiente pero que funciona: se puede hacer que se ejecuten funciones creando una línea AutoIt con la sustitución en la ER y luego ejecutándola con Execute. A ver si encuentro cómo lo hice y lo posteo...

Ya está, he hecho una nueva entrada para explicarlo bien:
http://www.emesn.com/autoitforum/viewto ... f=3&t=3133
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Jonny
Profesional del Autoit
Mensajes: 1042
Registrado: 30 Jun 2008, 20:08

Re: Sobre expresiones regulares

Mensaje por Jonny »

Bueno; la idea de la ER que ponía, era encontrar una parte de la cadena, basándome en una referencia, pero que la referencia sirviera sólo para encontrar esa parte de la cadena.

La idea era, encontrar los dígitos que hubieran después del carácter &, pero en realidad el & no me sirviera de nada, así, que la idea era que no me lo devolviera; que sólo devolviera los dígitos que hubieran posteriores a él, por eso el poner (?:\x26). Para que no me lo devolviera...
Tienes razón, & es imprimible y quizá se entiende mejor. Y en vez de [:digit:] \d queda más simplificado.

He leído el otro post. Tiene su lógica, aunque es una faena :). Tendré que estudiarlo con detenimiento, porque si no, hay que escribir más código del que sería necesario, y esa solución parece buena :P.

Salu2!
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

No hay que poner grupos no capturables para que no te capture las cosas... lo que no capturas no es capurado. (parece un trabalenguas pero no).
Sólo lo que está en grupos normales (capturables) quedan capturados para la solución o para referencias posteriores.
Bueno, también puede recuperarse toda la cadena que empareja con TODA la expresión regular, es lo que llaman full match (flags 2 y 4 de StringRegExp)

Así que cuando quieres capturar algo lo pones entre paréntesis, si no lo quieres capturar no lo pones entre paréntesis.

Los grupos no capturadores son para cuando no tienes más remedio que usar paréntesis para otra cosa, pero eso no lo quieres capturar. Por ejemplo cuando quieres agrupar algo con operadores OR "|" tienes que usar paréntesis, pero si eso no lo quieres capturar, que sólo pones los paréntesis por el OR, pues lo haces no capturable.

A ver que piense un ejemplo chorra... supongamos que en un texto queremos capturar las palabras "casa" y "pasa" en las expresiones "La casa" y "La pasa". La ER en principio sería:
La ((c|p)asa)
Pero como ahí hay dos grupos de paréntesis, en el texto "La casa es bonita" me devolverá dos matches: "casa" y "c".
La "c" viene por el paréntesis interior, pero eso no lo quiero en la solución, lo he puesto entre paréntesis por la baja precedencia del OR.
Así que para que no capture eso pero poder seguir usando el paréntesis se usa un grupo no capturable:
La ((?:c|p)asa)
Esto sólo devuelve "casa" como solución.

Respecto a lo otro... bueno, tú has preguntado. :smt003
¿Habría que escribir más código? Si tienes otra solución ponla, igual se puede acabar optimizando. A mí no se me ha ocurrido otra (usando ER, digo) y estuve bastante tiempo pensando que no había manera de ejecutar funciones para las referencias así que estoy bastante contento con esa solución, que aunque no sea maravillosa, al menos me permite hacer algo que llegué a dar por imposible.
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Jonny
Profesional del Autoit
Mensajes: 1042
Registrado: 30 Jun 2008, 20:08

Re: Sobre expresiones regulares

Mensaje por Jonny »

Respecto a lo otro... bueno, tú has preguntado.
¿Habría que escribir más código? Si tienes otra solución ponla, igual se puede acabar optimizando. A mí no se me ha ocurrido otra (usando ER, digo) y estuve bastante tiempo pensando que no había manera de ejecutar funciones para las referencias así que estoy bastante contento con esa solución, que aunque no sea maravillosa, al menos me permite hacer algo que llegué a dar por imposible.
:)

Cuando decía
He leído el otro post. Tiene su lógica, aunque es una faena :). Tendré que estudiarlo con detenimiento, porque si no, hay que escribir más código del que sería necesario, y esa solución parece buena :P.

me refería, a que sin usar esa solución que dices, para sustituir las coincidencias, habría que hacer más código. Me gusta más la solución que dices en el otro post :).

Salu2!
Jonny
Profesional del Autoit
Mensajes: 1042
Registrado: 30 Jun 2008, 20:08

Re: Sobre expresiones regulares

Mensaje por Jonny »

Aprovecho este hilo, ya que la cosa va de ER's, a ver si sabéis (seguro que sí) que hago mal con esta ER.

Quiero hacer una ER, que busque en una cadena todos los dígitos consecutivos, que justo delante tengan una \, tanto al principio de la cadena como en cualquier otra parte de esta, pero que a su vez, justo delante de \ no haya otro carácter \ :).

Le he dado ya montones de vueltas a la ER, pero no me acaba de salir.

En realidad, con la documentación de AutoIt sobre la sintaxis de las ER, sólo consigo entender que hace cada cosa, pero no como o cuando aplicarla. Se que (...) es un grupo, que ahora se que para capturar algo ha de estar entre () porque Ximorro lo ha explicado mejor, porque si es por la documentación, la única diferencia que entendía que había con [...], era que en un grupo
Los elementos en el grupo son tratados en orden y pueden ser repetidos juntos.
:)

Vamos, que entiendo qué es \A por ejemplo, pero no veo en la documentación como usarlo, sólo que es para indicar el principio de una línea. Sin embargo, me pongo a intentar buscar una cadena dentro de otra, que esté al principio de la cadena, y veo que no es lo mismo poner:

Código: Seleccionar todo

[\A]
que:

Código: Seleccionar todo

\A
Así, que debe ser eso, por lo que no me acaba de salir esta ER (por no entender como aplicar cada cosa).

Lo último que he hecho ha sido esto:

Código: Seleccionar todo

((\A\\[\d]+)|(^\\\\[\d]+))
que en:

Código: Seleccionar todo

$Text="\35\49"
me devuelve dos veces "\35".

quitando [] de las dos partes de la ER, el comportamiento es distinto, claro; pero va peor aún :).

Salu2!
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

Puede que la documentación no sea muy extensa pero léetela mejor.
Los corchetes se usan para indicar un grupo de caracteres, y sustituyen en la ER un carácter.
Por ejemplo la ER [mcgp]asa casará con "masa", "casa", "gasa" y "pasa".

No sé por qué miras lo del principio de la cadena si dices que buscas en cualquier parte de la cadena.
Quieres coger los números que estén después de "\", esa primera parte es fácil:
\\\d+
porque estamos hablando de cualquier número de dígitos ¿no? Si está limitado mejor lo pones para hacer la ER más eficiente, por ejemplo si siempre son códigos de dos cifras:
\\\d{2}

La otra parte es que delante del \ no haya otro \, eso ya es más complicado. Para los que no están al principio de la cadena (ahora sí hay que mirarlo) sería:
[^\\](\\\d+)

Para que tome también si está al principio junto con otros posteriores es más complicado, luego lo acabo, por ahora tengo:
(?:^|[^\\])(\\\d+)
que funciona bien tanto al princpio como en medio si no están seguidos, por ejemplo funciona con "\34a\23", pero no con "\34\23", porque al mirar antes de \23 que no haya otro \ se deberia mirar el 4, pero eso ya está consumido al tomar el grupo anterior...
Creo que hay que hacerlo con lo de las "assertions", luego lo miro.

Vale, ya lo tengo, efectivemente con una Lookbehind assertion se hace fácilmente:
http://www.autoitscript.com/autoit3/pcr ... html#SEC15

El problema de lo anterior es que las dos partes del OR (^|[^\\]) son diferentes en que ^ (que significa principio de cadena, similar a \A) no consume caracteres, en cambio [^\\] significa "un carácter que no es \", y eso sí consume, y eso hace más difícil la unión de las dos posibilidades (que antes de la cadena buscada haya algo diferente de \ o nada)

Pero con las assertion se pueden mirar partes de la cadena sin consumirlas, así que si usamos:
(?<!\\)(\\\d+)
creo que ya está resuelto, eso coge nuestra cadena si antes no hay un "\" (y eso incluye el principio de cadena), ya que por ejemplo para "\34\23" aunque el 34 se ha consumido en el primer match, para el segundo la aserción puede mirar el 4 para ver que no es un "\".
Por cierto, el grupo de la aserción no es capturable, así que eso sólo capturará \34 y \23. Ah, si no se quiere la barra, sólo los números, entonces es:
(?<!\\)\\(\d+)

Para mí que hay una manera sin aserciones, la verdad es que he estado cerca, pero no he dado con ello.

Ampliación:
Vale, respecto a tu expresión ((\A\\[\d]+)|(^\\\\[\d]+)) te explico a ver si entiendes un poco mejor.
Para empezar te devuelve dos veces lo mismo porque tienes toda la expresión entre paréntesis, con lo que te lo devuelve una vez por el grupo interior y otra por el global.
Por otro lado [\d] no hace falta ponerlo entre corchetes porque \d ya es un carácter numérico, \d es equivalente a [0123456789] (que también se puede escribir [0-9]). Se podría poner entre corchetes si fueras a añadirlo a más cosas pero si se usa por sí mismo te lo puedes ahorrar. Así que por ahora tenemos:
(\A\\\d+)|(^\\\\\d+)
(dad gracias que estamos en AutoIT, eso en los muchos lenguajes que doblan los \ sería: (\\A\\\\\\d+)|(^\\\\\\\\\\d+) :smt005 :smt005 :smt005

Por otro lado el carácter ^ significa "inicio de cadena", excepto dentro de un conjunto (corchetes), que es cuando es la negación del conjunto. Así que para poner "un carácter que no es \" tienes que ponerlo, esta vez sí, entre corchetes: [^\\], así que tenemos:
(\A\\\d+)|([^\\]\\\d+)
Pero ese "carácter que no es \" no lo quieres capturar, así que lo sacamos del paréntesis:
(\A\\\d+)|[^\\](\\\d+)

Y eso casi funciona, pero tiene el problema que tenía yo en la última versión sin aserciones, si te fijas es lo mismo pero yo agrupé el factor común \\\d+.
La otra diferencia es que yo usaba ^ en vez de \A, que son equivalentes cuando no hay retornos de carro dentro de la cadena.
El problema ya lo he comentado antes pero lo detallo. Por ejemplo para \34\23, la expresión (\A\\\d+) capturará el primer \34, ahora queda por analizar la subcadena \23 pero:
(\A\\\d+) no hace match con ella pues no está al principio de cadena.
[^\\](\\\d+) tampoco hace match con ella porque antes de \\\d+ ("una barra seguida de números", o sea, toda la subcadena \23) NO tiene [^\\] (un carácter que no es \), y no lo tiene porque no hay nada.
Como decía antes nos puede parecer que el 4 anterior vale de "carácter que no es \", pero no es utilizado en el análisis porque ya ha sido consumido para el primer match (por eso funciona el método de la aserción, porque precisamente sirve para analizar la cadena sin consumirla)
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Jonny
Profesional del Autoit
Mensajes: 1042
Registrado: 30 Jun 2008, 20:08

Re: Sobre expresiones regulares

Mensaje por Jonny »

¡UUUUUUUAAAAAUUUUUUUUUUUUU!

Gracias, porque además de la solución, este es uno de esos posts para guardarse, porque es una muy buena lección.

Yo, lo que entiendo de la ayuda, es que efectivamente, [...] es para indicar un conjunto de carácteres. Pero, en cualquier órden, mientras que (...) también sirve para indicar conjuntos de carácteres, en un órden.
Eso es lo que entiendo, luego; no parece que sea exactamente así, como estoy viendo estos días que hablamos de ER's.

Por ejemplo, (volveré a mirarme la ayuda por siacaso) pero no recuerdo haber visto que ^ indique el inicio de cadena, excepto entre [] como dices, ahora me explico que por más vueltas que le daba, entre otras cosas, no me funcionara.

En la ayuda, sobre este carácter, lo que veo es:
Coincide cualquier carácter no en el set. Eje. [^0-9] coincide con cualquier no-dígito. Para incluir un caret (^) en un set, ponerlo después de empezar de establecer o escaparlo (\^).
Que esto sí, entiendo que es la negación de un conjunto de carácteres, indicados entre [].
^ y $ coincide con nuevas líneas dentro de datos.
¿Será esto?...
¿assertions?
?Lookbehind?
No había oído eso nunca :).
Puf. La especificación en inglés tengo que mirármela más despacio :)...

¿A qué te refieres con "consumir carácteres", a que si coincide en un match no coincide con el siguiente, si los carácteres están correlativos?.
Por lo que explicas al final del post entiendo que debe ser algo así.

Uf, las ER son todo un mundo, con mucha tela ... :).

Salu2!
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

Me alegro que te sea útil, que conste que no soy un especialista ni mucho menos, normalmente veo una ER terminada y me parece magia ¡¡incluso las que hago yo!! :smt005

Vamos a ver, me temo que no tienes las ideas muy claras sobre corchetes y paréntesis, no tienen nada que ver y me temo que no es como dices.
Los corchetes sirven para especificar UN SOLO carácter de un conjunto dado, nada de TODOS de forma desordenada.
Así como te decía antes la expresión [mcgp]asa empareja con las cadenas "masa", "casa", "gasa" y "pasa". ¡Fíjate que es UNO de los caracteres del corchete.
NO empareja con mcasa, por ejemplo (no globalmente, sí si buscas subcadena, pero entonces es porque encuentra la subcadena casa, igualmente el corchete sólo sustituye un carácter)

Los paréntesis son para agrupar, no tienen nada que ver con el orden de los caracteres. También sirven para dar precedencia a operadores como el OR (la barra) y para crear otras expresiones especiales (como lo de las aserciones, condicionales, etc...)
En un expresión regular para poner un conjunto ordenado de caracteres no hay que hacer nada, simplemente ponerlos. En mi expresión anterior [mcgp]asa los caracteres asa indican que hay que emparejar con esos tres caracteres en orden, no es necesario poner el paréntesis. Lo puedes poner si quieres capturar ese grupo, pero eso no tiene que ver con el orden.
De hecho puedes poner paréntesis a los corchetes, para ver qué carácter ha encontrado: ([mcgp])asa

Lo del ^ como inicio de línea es cierto que en la ayuda básica no está especificado, aunque sí que está explicado que cuando está entre corchetes significa (un carácter que no pertenezca a ese conjunto).
^ y $ dentro de corchetes pierden su significado especial (como casi todos los caracteres especiales).

Sí hay una referencia a ^ y $ cuando cuenta el flag multilínea (?m).

Yo esas cosas las he aprendido en la ayuda extendida, enlazada en la ayuda de StringRegExp, y que lleva aquí (me temo que en inglés):
http://www.autoitscript.com/autoit3/pcrepattern.html
Lo de las aserciones lookbehind y lookahead y muchas cosas más las aprendí ahí.

O mejor dicho, gracias a eso sé que existe, porque cada vez que tengo que utilizarlo tengo que mirar la ayuda a ver cómo se ponía :smt003 como te digo no soy ningún experto.

Buf, lo de consumir si no has entendido la explicación de antes no sé explicarlo mucho mejor, a ver si con más detalle:
Para emparejar con la ER se va recorriendo la cadena, por ejemplo si hacemos
StringRegExp("1234", "\d{2}", 3)
para que nos dé todos los conjuntos de dos cifras, veremos que nos devuelve un vector con "12" y "34", dos soluciones.
¡NO nos da la cadena "23", aunque también eso son dos cifras consecutivas!
Y es porque la ER va siguiendo la cadena de test "1234" en orden, primero mira el "1" y efectivamente eso cuadra con el primer carácter de la ER (un dígito), después mira el segundo, el "2" y eso cuadra con el segundo carácter de la ER (otro dígito, "\d{2}" es equivalente a "\d\d"). Eso termina con la ER, ha encontrado un emparejamiento (match en inglés), así que guarda "12" como un resultado Y CONTINUA MIRANDO LA CADENA DONDE LO HA DEJADO, o sea, con el "3". No usa el "2" PORQUE YA LO HA CONSUMIDO PARA EMPAREJAR EL RESULTADO ANTERIOR. El "3" vuelve a cuadrar como primer carácter de la ER, y luego pasa al 4 que empareja con el segundo, así que ese "34" empareja con toda la ER y lo almacena como otro match.

Cuando se analizan ER sólo se hace backtraking cuando fallan, para ver si otro tipo de emparejamiento no falla. Cuando se encuentra coincidencia se continúa analizando la cadena, no se vuelve al principio ni hacia atrás de ninguna manera (supongo que eso crearía bucles infinitos muy fácilmente).
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Avatar de Usuario
ainurzzz
Mensajes: 27
Registrado: 09 Sep 2007, 15:04

Re: Sobre expresiones regulares

Mensaje por ainurzzz »

Por si puede servir.... voy a empollarme este tema, muy necesario y que desde hace mas de 10 años no veo. Lo usé en su momento con awk, creo....

Imagen


Obtenido de:http://www.addedbytes.com/cheat-sheets/ ... version-1/
Avatar de Usuario
Ximorro
Profesional del Autoit
Mensajes: 1500
Registrado: 10 Jul 2009, 12:35
Ubicación: Castellón, España

Re: Sobre expresiones regulares

Mensaje por Ximorro »

Muchas gracias ainurzz, desgraciadamente no es de completa aplicación para autoit, no sé que "sabor" será pero no es PCRE, que es el motor que usa AutoIt.
Pero no quiere decir que no sea útil, al contrario, como tabla resumen está muy bien así que se puede usar mucho, simplemente si algo no funciona mirad en concreto la ayuda completa de PCRE a ver si es que no es compatible en eso que estáis usando.

Así rápido por ejemplo veo que \< y \> no están en PCRE. Las opciones no se ponen entre barras, sino con (?opcion), lo de octal no me suena nada... Pero en general casi todos los formatos de ER derivan del viejo grep, así que tienen muchas cosas comunes y gran mayoría de la tabla será muy útil.

Para completar el conocimiento de PCRE en concreto recomiendo mirar la página que enlaza desde la ayuda:
http://www.autoitscript.com/autoit3/pcrepattern.html

La página de PCRE es más que espartana :-(
http://www.pcre.org/

Aquí he encontrado un tutorial buenísimo. No está asociado a ningún tipo de motor de ER concreto, sino que es genérico y va diciendo las diferencias que hay en los diferentes motores (eso sí, en inglés, sorry):
http://www.regular-expressions.info/tutorial.html
"¿Y no será que en este mundo hay cada vez más gente y menos personas?". Mafalda (Quino)
Responder