总览
正则表达式(AKA regex)是一种形式语言,用于定义具有某种模式的字符序列。 在现实世界中,它们可以用于解决半结构化文本的许多问题。 您可以从带有大量修饰或不相关内容的文本中提取重要的片段。 Go在其标准库中具有强大的regex软件包,可让您使用正则表达式对文本进行切片和切块。
在这个由两部分组成的系列文章中,您将学习什么是正则表达式以及如何在Go中有效使用正则表达式来完成许多常见任务。 如果您根本不熟悉正则表达式,那么有很多很棒的教程。 这是一个好人 。
了解正则表达式
让我们从一个简单的例子开始。 您有一些文本,并且想要检查它是否包含电子邮件地址。 电子邮件地址是在RFC 822中严格指定的。 简而言之,它有一个局部部分,后跟一个@符号,后跟一个域。 邮件地址将与其他文本用空格分隔。
要弄清楚它是否包含电子邮件地址,可以使用以下正则表达式: ^\w+@\w+\.\w+$
。 请注意,此正则表达式有点宽容,将允许一些无效的电子邮件地址通过。 但这足以说明这个概念。 在解释其工作方式之前,让我们在几个潜在的电子邮件地址上进行尝试:
package main
import (
"os"
"regexp"
"fmt"
)
func check(err error) {
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func main() {
emails := []string{
"brown@fox",
"brown@fox.",
"brown@fox.com",
"br@own@fox.com",
}
pattern := `^\w+@\w+\.\w+$`
for _, email := range emails {
matched, err := regexp.Match(pattern, []byte(email))
check(err)
if matched {
fmt.Printf("√ '%s' is a valid email\n", email)
} else {
fmt.Printf("X '%s' is not a valid email\n", email)
}
}
}
Output:
X 'brown@fox' is not a valid email
X 'brown@fox.' is not a valid email
√ 'brown@fox.com' is a valid email
X 'br@own@fox.com' is not a valid email
我们的正则表达式适用于此小样本。 前两个地址被拒绝,因为域没有点或点后没有任何字符。 第三封电子邮件的格式正确。 最后一个候选人有两个@符号。
让我们分解一下此正则表达式: ^\w+@\w+\.\w+$
字符/符号 | 含义 |
---|---|
^ | 目标文本的开头 |
\ w | 任何文字字符[0-9A-Za-z_] |
+ | 至少一个以上字符 |
@ | 从字面上看@字符 |
\。 | 文字点字符。 必须用\ |
$ | 目标文字结尾 |
总而言之,此正则表达式将匹配以一个或多个单词字符开头,其后是“ @”字符,再由一个或多个单词字符,再由一个点,再由一个或多个单词字符开头的文本。
处理特殊字符
以下字符在正则表达式中具有特殊含义: .+*?()|[]{}^$\
。 我们已经在电子邮件示例中看到了很多。 如果我们想从字面上匹配它们,则需要使用反斜杠将其转义。 让我们介绍一个名为match()
辅助函数,它可以节省很多输入时间。 它需要一个模式和一些文本,使用regexp.Match()
方法将模式与文本匹配(将文本转换为字节数组之后),并输出结果:
func match(pattern string, text string) {
matched, _ := regexp.Match(pattern, []byte(text))
if matched {
fmt.Println("√", pattern, ":", text)
} else {
fmt.Println("X", pattern, ":", text)
}
}
这是匹配常规字符(如z
与匹配特殊字符(如?
的示例 :
func main() {
text := "Can I haz cheezburger?"
pattern := "z"
match(pattern, text)
pattern = "\\?"
match(pattern, text)
pattern = `\?`
match(pattern, text)
}
Output:
√ z : Can I haz cheezburger?
√ \? : Can I haz cheezburger?
√ \? : Can I haz cheezburger?
正则表达式模式\?
包含一个反斜杠,当表示为常规Go字符串时,必须使用另一个反斜杠进行转义。 原因是反斜杠还用于转义Go字符串中的特殊字符,例如换行符( \n
)。 如果要匹配反斜杠字符本身,则需要四个斜杠!
解决方案是使用带有反引号( `
)的Go原始字符串,而不是双引号。 当然,如果要匹配换行符,则必须返回常规字符串并处理多个反斜杠转义符。
占位符和重复
在大多数情况下,您不会尝试从字面上匹配一系列特定字符(例如“ abc”),而是将长度未知的序列与某些已知字符注入某个位置。 正则表达式通过点支持此用例.
代表任何字符的特殊字符。 *
特殊字符将前一个字符(或组)重复零次或多次。 如果将它们组合在一起(如.*
,那么您将匹配任何内容,因为它仅意味着零个或多个字符。 +
与*
非常相似,但是它匹配一个或多个以前的字符或组。 因此, .+
将匹配任何非空文本。
使用边界
边界有三种类型: ^
表示的文本开头, $
表示的文本结尾和\b
表示的单词边界。 例如,请考虑经典电影《公主新娘 》中的这段文字:“我叫Inigo Montoya。您杀了我父亲。准备死。” 如果仅匹配“父亲”,则会得到匹配,但是如果您在文本末尾查找“父亲”,则需要添加$
字符,然后将没有匹配项。 另一方面,匹配开头的“ Hello”效果很好。
func main() {
text := "Hello, my name is Inigo Montoya, you killed my father, prepare to die."
pattern := "father"
match(pattern, text)
pattern = "father$"
match(pattern, text)
pattern = "^Hello"
match(pattern, text)
}
Output:
√ father : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X father$ : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ ^Hello : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
单词边界看每个单词。 您可以使用\b
来开始和/或结束模式。 请注意,标点符号(例如逗号)被视为边界,而不是单词的一部分。 这里有一些例子:
func main() {
text := `Hello, my name is Inigo Montoya,
you killed my father, prepare to die.`
pattern := `kill`
match(pattern, text)
pattern = `\bkill`
match(pattern, text)
pattern = `kill\b`
match(pattern, text)
pattern = `\bkill\b`
match(pattern, text)
pattern = `\bkilled\b`
match(pattern, text)
pattern = `\bMontoya,\b`
match(pattern, text)
}
Output:
√ kill : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ \bkill : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X kill\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X \bkill\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
√ \bkilled\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
X \bMontoya,\b : Hello, my name is Inigo Montoya,
you killed my father, prepare to die.
使用课程
将所有字符组像所有数字,空格字符或所有字母数字字符一样对待通常很有用。 Golang支持POSIX类,它们是:
角色类 | 含义 |
---|---|
[:alnum:] | 字母数字(≡[0-9A-Za-z]) |
[:α:] | 字母(≡[A-Za-z]) |
[:ascii:] | ASCII(≡[\ x00- \ x7F]) |
[:空白:] | 空白(≡[\ t]) |
[:cntrl:] | 控制(≡[\ x00- \ x1F \ x7F]) |
[:数字:] | 位数(≡[0-9]) |
[:图形:] | 图形(≡[!-〜] == [A-Za-z0-9!“#$%&'()* +,\-。/ :; <=>?@ [\\\] ^ _`{ |}〜]) |
[:降低:] | 小写(≡[az]) |
[:打印:] | 可打印的(≡[-〜] == [[:graph:]]) |
[:punct:] | 标点符号(≡[!-/:-@@ [-`{-〜]) |
[:空间:] | 空格(≡[\ t \ n \ v \ f \ r]) |
[:上:] | 大写(≡[AZ]) |
[:字:] | 字字符(≡[0-9A-Za-z_]) |
[:xdigit:] | 十六进制数字(≡[0-9A-Fa-f]) |
在下面的示例中,我将使用[:digit:]
类在文本中查找数字。 另外,我在这里展示了如何通过在花括号中添加请求的数字来搜索确切的字符数。
func main() {
text := `The answer to life, universe and
everything is 42 ."
pattern := "[[:digit:]]{3}"
match(pattern, text)
pattern = "[[:digit:]]{2}"
match(pattern, text)
}
Output:
X [[:digit:]]{3} : The answer to life, universe and
everything is 42.
√ [[:digit:]]{2} : The answer to life, universe and
everything is 42.
您也可以通过将字符放在方括号中来定义自己的类。 例如,如果要检查某些文本是否为仅包含字符ACGT
的有效DNA序列,则使用^[ACGT]*$
正则表达式:
func main() {
text := "AGGCGTTGGGAACGTT"
pattern := "^[ACGT]*$"
match(pattern, text)
text = "Not exactly a DNA sequence"
match(pattern, text)
}
Output:
√ ^[ACGT]*$ : AGGCGTTGGGAACGTT
X ^[ACGT]*$ : Not exactly a DNA sequence
使用替代品
在某些情况下,有多种可行的选择。 匹配的HTTP URL可以通过协议模式来表征,该协议模式可以是http://
或https://
。 管道字符|
让您在其他选择之间进行选择。 这是一个将它们整理出来的正则表达式: (http)|(https)://\w+\.\w{2,}
。 它转换为以http://
或https://
开头的字符串,后跟至少一个单词字符,然后是一个点,然后是至少两个单词字符。
func main() {
pattern := `(http)|(https)://\w+\.\w{2,}`
match(pattern, "http://tutsplus.com")
match(pattern, "https://tutsplus.com")
match(pattern, "htt://tutsplus.com")
}
Output:
√ (http)|(https)://\w+\.\w{2,} : http://tutsplus.com
√ (http)|(https)://\w+\.\w{2,} : https://tutsplus.com
X (http)|(https)://\w+\.\w{2,} : htt://tutsplus.com
结论
在本部分的教程中,我们通过使用Golang regexp库的动手示例覆盖了很多基础知识,并且学习了很多有关正则表达式的知识。 我们专注于纯匹配以及如何使用正则表达式表达我们的意图。
在第二部分中,我们将集中于使用正则表达式来处理文本,包括模糊查找,替换,分组和处理新行。
翻译自: https://code.tutsplus.com/tutorials/regular-expressions-with-go-part-1--cms-30403