Ruby regex - LPC-Ltda/Ruby-on-Rails GitHub Wiki
Las expresiones regulares de Ruby (ruby regex para abreviar) lo ayudan a encontrar patrones específicos dentro de las cadenas, con la intención de extraer datos para su posterior procesamiento.
Dos casos de uso comunes para las expresiones regulares incluyen la validatión & parsing.
Por ejemplo:
Piense en una dirección de correo electrónico, con una expresión regular de ruby puede definir cómo se ve una dirección de correo electrónico válida. En otras palabras, su programa podrá diferenciar entre una dirección de correo electrónico válida y una no válida.
Las expresiones regulares de Ruby se definen entre dos barras diagonales para diferenciarlas de la sintaxis de otros idiomas. Las expresiones más simples coinciden con una palabra o incluso una sola letra.
Por ejemplo:
# Find the word 'like'
"Do you like cats?" =~ /like/
Esto devuelve el índice de la primera aparición de la palabra si se encontró o nil en caso contrario. Si no nos importa el índice, podríamos usar el método String#include?
.
Otra forma de verificar si una cadena coincide con una expresión regular es usar el método de match
:
if "Do you like cats?".match(/like/)
puts "Match found!"
end
Ahora aprenderá a crear patrones más avanzados para que pueda hacer coincidir, capturar y reemplazar cosas como fechas, números de teléfono, URL, etc.
Una clase de carácter le permite definir un rango o una lista de caracteres para que coincidan. Por ejemplo, [aeiou]
coincide con cualquier vocal.
Por ejemplo: el string contiene una vocal?
def contains_vowel(str)
str =~ /[aeiou]/
end
contains_vowel("test") # returns 1
contains_vowel("sky") # returns nil
Esto no tendrá en cuenta la cantidad de caracteres, veremos cómo hacerlo pronto.
Podemos usar rangos para hacer coincidir varias letras o números sin tener que escribirlos todos. En otras palabras, un rango como [2-5]
es lo mismo que [2345]
.
Algunos rangos útiles:
-
[0-9]
coincide con cualquier número del 0 al 9 -
[a-z]
coincide con cualquier letra de la a a la z (minúsculas) -
[^ a-z]
rango negado
Ejemplo: ¿Este string contiene números?
def contains_number(str)
str =~ /[0-9]/
end
contains_number("The year is 2015") # returns 12
contains_number("The cat is black") # returns nil
Recuerde: el valor de retorno cuando se usa
= ~
es el índice del string onil
Hay sintaxis abreviada para especificar rangos de caracteres:
-
\w
es equivalente a[0-9a-zA-Z_]
-
\d
es lo mismo que[0-9]
-
\s
coincide con el espacio en blanco (tabulaciones, espacio regular, nueva línea)
También existe la forma negativa de estos:
-
\W
cualquier cosa que no esté en[0-9a-zA-Z_]
-
\D
cualquier cosa que no sea un número -
\S
cualquier cosa que no sea un espacio
El carácter de punto .
coincide con todo menos nuevas líneas. Si necesita usar un .
literal entonces tendrás que escaparlo.
Ejemplo: escapando caracteres especiales
# If we don't escape, the letter will match
"5a5".match(/\d.\d/)
# In this case only the literal dot matches
"5a5".match(/\d\.\d/) # nil
"5.5".match(/\d\.\d/) # match
Hasta ahora solo hemos podido hacer coincidir un solo personaje a la vez. Para hacer coincidir varios caracteres, podemos usar modificadores de patrones.
+ | 1 o más | * | 0 o más | ? | 0 o 1 | {3,5} | entre 3 y 5 |
Podemos combinar todo lo que hemos aprendido hasta ahora para crear expresiones regulares más complejas.
Ejemplo: ¿Parece una dirección IP?
# Note that this will also match some invalid IP address
# like 999.999.999.999, but in this case we just care about the format.
def ip_address?(str)
# We use !! to convert the return value to a boolean
!!(str =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
end
ip_address?("192.168.1.1") # returns true
ip_address?("0000.0000") # returns false
Si necesita coincidencias exactas, necesitará otro tipo de modificador. Veamos un ejemplo para que veas de qué estoy hablando:
# We want to find if this string is exactly four letters long, this will
# still match because it has more than four, but it's not what we want.
"Regex are cool".match /\w{4}/
# Instead we will use the 'beginning of line' and 'end of line' modifiers
"Regex are cool".match /^\w{4}$/
# This time it won't match. This is a rather contrived example, since we could just
# have used .size to find the length, but I think it gets the idea across.
Si desea hacer coincidir estrictamente al comienzo de un string y no solo en cada línea (después de \n
), debe usar \A
y \Z
en lugar de ^
y $
.
Con los grupos capture, podemos capturar parte de una coincidencia y reutilizarla más tarde. Para capturar una coincidencia incluimos la parte que queremos capturar entre paréntesis.
Ejemplo: Analizando un archivo Log
Line = Struct.new(:time, :type, :msg)
LOG_FORMAT = /(\d{2}:\d{2}) (\w+) (.*)/
def parse_line(line)
line.match(LOG_FORMAT) { |m| Line.new(*m.captures) }
end
parse_line("12:41 INFO User has logged in.")
# This produces objects like this:
# <struct line="" time="12:41" ,="" type="INFO" msg="User has logged in.">
</struct>
En este ejemplo, usamos .match
en lugar de =~
.
Este método devuelve un objeto MatchData
si hay una coincidencia, nil
en caso contrario. La clase MatchData
tiene muchos métodos útiles, consulte la documentacion para obtener más información.
Si solo desea un valor booleano (true
/false
), ¿puede usar el método match?
, que está disponible desde Ruby 2.4. Esto también es más rápido que match
, ya que Ruby no necesita crear un objeto MatchData
.
Puede acceder a los datos capturados utilizando el método .captures
o tratando el objeto MatchData
como un arreglo, el índice cero tendrá la coincidencia completa y los índices consiguientes contendrán los grupos coincidentes.
Si desea el primer grupo de captura, puede hacer esto:
m = "John 31".match /\w+ (\d+)/
m[1]
# 31
También puede tener grupos que no capturan. Le permitirán agrupar expresiones sin penalizar el rendimiento. También puede encontrar útiles los grupos con nombre para facilitar la lectura de expresiones complejas.
`(?:...)` | grupo de no captura | `(?...)` | grupo nombrado |
Ejemplo: grupos con nombre
m = "David 30".match /(?<name>\w+) (?<age>\d+)/
m[:age]
# => "30"
m[:name]
# => "David"
</age></name>
Un grupo con nombre devuelve un objeto MatchData
al que puede acceder para leer los resultados.
Esta es una técnica más avanzada que puede no estar disponible en todas las implementaciones de expresiones regulares. El motor de expresiones regulares de Ruby es capaz de hacer esto, así que veamos cómo aprovecharlo.
Look ahead nos permite echar un vistazo y ver si hay una coincidencia específica antes o después.
`(?=pat)` | lookahead positivo | `(?<=pat)` | lookbehind positivo | `(?!pat)` | lookahead negativo | `(? | lookbehind negativo |
Ejemplo: ¿hay un número precedido por al menos una letra?
def number_after_word?(str)
!!(str =~ /(?<=\w) (\d+)/)
end
number_after_word?("Grade 99")
Las expresiones regulares de Ruby son instancias de la clase Regexp
. La mayoría de las veces no utilizará esta clase directamente, pero es bueno saber.
puts /a/.class
# Regexp
Un posible uso es crear una expresión regular a partir de un string:
regexp = Regexp.new("a")
Otra forma de crear una expresión regular:
regexp = %r{\w+}
`i` | no sensible a las mayusculas | `m` | punto coincide con nueva línea | `x` | ignore espacios en blanco |
Para usar estas opciones, agregue la letra al final de la expresión regular, después del cierre /
.
"abc".match?(/[A-Z]/i)
Las expresiones regulares complejas de Ruby pueden resultar bastante difíciles de leer, por lo que será útil dividirlas en varias líneas. Podemos hacer esto usando el modificador 'x'. Este formato también le permite utilizar comentarios dentro de su expresión regular.
LOG_FORMAT = %r{
(\d{2}:\d{2}) # Time
\s(\w+) # Event type
\s(.*) # Message
}x
Las expresiones regulares se pueden usar con muchos métodos Ruby.
- .split
- .scan
- .gsub
- y muchas más
Ejemplo: haga coincidir todas las palabras de un string usando .scan
"this is some string".scan(/\w+/)
# => ["this", "is", "some", "string"]
Ejemplo: extraer todos los números de un string.
"The year was 1492.".scan(/\d+/)
# => ["1492"]
Ejemplo: poner en mayúscula todas las palabras de un string
str = "lord of the rings"
str.gsub(/\w+/) { |w| w.capitalize }
# => "Lord Of The Rings"
Ejemplo: validar una dirección de correo electrónico
email = "[email protected]"
!!email.match(/\A[\w.+-]+@\w+\.\w+\z/)
# true
Este último ejemplo usa !!
para convertir el resultado en un valor booleano (true
/false
), alternativamente, puede usar match?
en Ruby 2.4+ que ya hace esto por ti y también es más rápido.