Las expresiones regulares ("Regexp") son una herramienta compliacada pero poderosa para la coincidencia de patrones y manipulación del texto. Sin embargo no se desempeñan tan bien como las coincidencias de texto puro, son mas flexibles. Basadas en su sintaxis, puedes filtrar casi cualquier tipo de texto de tu fuente d econtenidos. Si quieres recolectar datos en desarrollo web, no es dificil utilizar las Expresiones regulares para obtener datos importantes.
Go tiene un paquete regexp
, que provee el soporte oficial para expresiones regulares. Si ya has usado expresiones regulares en otros lenguajes de programación, puedes estar familiarizado con ellas. Nota que Go implemente el estándar RE2, excepto por \C
. Para mas detalles sigue el siguiente enlace: http://code.google.com/p/re2/wiki/Syntax.
El paquete strings
de Go puede hacer muchos trabajos como búsqueda (Contains, Index), reemplace, (Replace), análisis (Split, Join), etc. y es mucho mas rápido que las expresiones regulares. Sin embargo, todas esas son operaciones triviales. Si quieres buscar una cadena sin tener en cuenta mayúsculas o minúsculas, las expresiones regualres serán tu mejor opción. Entonces, si el paquete strings
es suficiente para tus necesidades, úsalo porque es mas fácil de leer y entender; si necesitas hacer operaciones mas avanzadas, usa las expresiones regulares.
Si recuerdas la sección anterior de validación de formularios, usamos expresiones regulares para validar la información de entrada del usuario. Se conciente que todos los caracteres están en UTF-8. ¡Vamos a aprender mas del paquete regexp
!.
El paquete regexp
tiene tres funciones para coincidencias: Si encuentra una coincidencia retorna true, de lo contrario false.
func Match(pattern string, b []byte) (matched bool, error error)
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
func MatchString(pattern string, s string) (matched bool, error error)
Las tres funciones verifican si pattern
tiene una coincidencia en la entrada. retornando true si hay coincidencia. Sin embargo, si la expresión regular tiene errores, retornará un error. Los tres tipos de entradas aquí son []byte
, RuneReader
y string
.
Aquí está un ejemplo de como verificar una dirección IP:
func IsIP(ip string) (b bool) {
if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
return false
}
return true
}
Como puedes ver, usando un patrón en el paquete regexp
no es tan diferente. Aquí hay otro ejemplo de verificar si la entrada de un usuario es válida:
func main() {
if len(os.Args) == 1 {
fmt.Println("Usage: regexp [string]")
os.Exit(1)
} else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
fmt.Println("Number")
} else {
fmt.Println("Not number")
}
}
En el ejemplo anterior usamos Match(Reader|String)
para verificar si el contenido es álido, y ambos son fáciles de usar.
El modo Match puede verificar el contenido pero no cortarlo, filtrarlo o recoger información de él. Si quieres hacer esto, tienes que usar el modo complejo de las regexp
.
Digamos que necesitamos escribir un crawler. Aquí un ejemplo de como usar las expresiones regulares para filtrar y cortar los datos.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
)
func main() {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
fmt.Println("http get error.")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("http read error")
return
}
src := string(body)
// Convertimos las etiquetas HTML a minúsculas.
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
src = re.ReplaceAllStringFunc(src, strings.ToLower)
// Removemos los estilos..
re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
src = re.ReplaceAllString(src, "")
// Removemos los scripts.
re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
src = re.ReplaceAllString(src, "")
// Removemos todas las etiquetas html y las reemplazamos con saltos de línea.
re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
src = re.ReplaceAllString(src, "\n")
// Removemos los saltos de línea contínuos.
re, _ = regexp.Compile("\\s{2,}")
src = re.ReplaceAllString(src, "\n")
fmt.Println(strings.TrimSpace(src))
}
En este ejemplo, usamos Compile como un primer paso para el modo complejo. Esto verifica que la sintaxis de la expresión regular esté correcta, entonces retorna la regexp
para analizar el contenido en otras operaciones.
Aquí están algunas funciones para analizar la sintaxis de las expresiones regulares:
func Compile(expr string) (*Regexp, error)
func CompilePOSIX(expr string) (*Regexp, error)
func MustCompile(str string) *Regexp
func MustCompilePOSIX(str string) *Regexp
La diferencua entre ComplePOSIX
y Compile
es que el formador no tiene que usar la sintaxis POSIX cuando es una búsqueda de mas a la izquierda, y en el otro, únicamente la búsqueda de mas a la izquierda. Por ejemplo, para la expresión [a-z]{2,4}
y el contenido "aa09aaa88aaaa"
, CompilePOSIX
retornará aaa
pero Compile
retornará aa
. El prefijo Must
significa un panic cuando la expresión regular no es correcta, retornando un error de otra manera.
Ahora que sabemos como crear expresiones regulares, vamos a ver cómo los métodos nos proveen una manera para operar con el contenido:
func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllString(s string, n int) []string
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
func (re *Regexp) FindString(s string) string
func (re *Regexp) FindStringIndex(s string) (loc []int)
func (re *Regexp) FindStringSubmatch(s string) []string
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int
Estos 18 métodos contienen funciones idénticas para distintos tipos de entredad (segmentos, cadenas e io.RuneReader), entonces vamos a simplificar la lista ignorando algunos tipos de entrada como sigue:
func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int
Código de ejemplo:
package main
import (
"fmt"
"regexp"
)
func main() {
a := "I am learning Go language"
re, _ := regexp.Compile("[a-z]{2,4}")
// Encontrar la primera coincidencia.
one := re.Find([]byte(a))
fmt.Println("Find:", string(one))
// Encontrar todas las coincidencias y guardarlas en un segmento, n menor a 0 significa todas las coincidencias, indicar el tamaño del segmento si es mayor a 0.
all := re.FindAll([]byte(a), -1)
fmt.Println("FindAll", all)
// Encuentra el índice de la primera coincidencia, posición de inicio y fin.
index := re.FindIndex([]byte(a))
fmt.Println("FindIndex", index)
// Encontrar los índices de todas las coincidencias, n hace lo mismo que el de arriba.
allindex := re.FindAllIndex([]byte(a), -1)
fmt.Println("FindAllIndex", allindex)
re2, _ := regexp.Compile("am(.*)lang(.*)")
// Encuentra la primera subcoincidencia y retorna un arreglo, el primero elemento contiene todos los elementos, el segundo contiene el resultado del primer (), en tercero, los resultados del segundo ()
// Salida:
// Primer elemento: "am learning Go language"
// Segundo elemento: " learning Go ", Note que los espacios están en la salida.
// Tercer elemento: "uage"
submatch := re2.FindSubmatch([]byte(a))
fmt.Println("FindSubmatch", submatch)
for _, v := range submatch {
fmt.Println(string(v))
}
// Igual que FindIndex().
submatchindex := re2.FindSubmatchIndex([]byte(a))
fmt.Println(submatchindex)
// FindAllSubmatch, Encontrar todas las subcoincidencias.
submatchall := re2.FindAllSubmatch([]byte(a), -1)
fmt.Println(submatchall)
// FindAllSubmatchIndex, Encontrar todas las subcoincidencias.
submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
fmt.Println(submatchallindex)
}
Como mencionamos anteriormente, Las expresiones regulares tienen 3 métodos para coincidencias, ellos hace exactamente lo mismo que las funciones exportadas. De hecho, estas funciones pueden ser exportadas:
func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool
Ahora veamos como reemplazar cadenas usando expresiones regulares:
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
func (re *Regexp) ReplaceAllString(src, repl string) string
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
Estos son usadas en el ejemplo del crawler, entonces no se explicarán mas aquí.
Veamos la definición de Expand
:
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
¿Cómo usamos Expand
?
func main() {
src := []byte(`
call hello alice
hello bob
call hello eve
`)
pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
res := []byte{}
for _, s := range pat.FindAllSubmatchIndex(src, -1) {
res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
}
fmt.Println(string(res))
}
A este punto, hemos aprendido el paquete entero de regexp
en Go, espero que ahora puedas entender más usando los métodos claves, entonces podrás hacer algo interesante por ti mismo.
- Índice
- Sección previa: JSON
- Siguiente sección: Plantillas