06 转义:正则中转义需要注意哪些问题?
你好,我是伟忠。今天我来和你聊聊转义。转义对我们来说都不算陌生,编程的时候,使用到字符串时,双引号里面如果再出现双引号,我们就可以通过转义来解决。就像下面这样:
str = "How do you spell the word \"regex\"?"
虽然转义在日常工作中比较常见,但正则中什么时候需要转义,什么时候不用转义,在真正使用的时候可能会遇到这些麻烦。所以我们很有必要来系统了解一下正则中的转义。
转义字符
首先我们说一下什么是转义字符(Escape Character)。它在维基百科中是这么解释的:
在计算机科学与远程通信中,当转义字符放在字符序列中,它将对它后续的几个字符进行替代并解释。通常,判定某字符是否为转义字符由上下文确定。转义字符即标志着转义序列开始的那个字符。
这么说可能有点不好理解,我再来给你通俗地解释一下。转义序列通常有两种功能。第一种功能是编码无法用字母表直接表示的特殊数据。第二种功能是用于表示无法直接键盘录入的字符(如回车符)。
我们这节课说的就是第二种情况,转义字符自身和后面的字符看成一个整体,用来表示某种含义。最常见的例子是,C语言中用反斜线字符“\”作为转义字符,来表示那些不可打印的ASCII控制符。另外,在URI协议中,请求串中的一些符号有特殊含义,也需要转义,转义字符用的是百分号“%”。之所以把这个字符称为转义字符,是因为它后面的字符,不是原来的意思了。
在日常工作中经常会遇到转义字符,比如我们在shell中删除文件,如果文件名中有*号,我们就需要转义,此时我们能看出,使用了转义字符后,*号就能放进文件名里了。
rm access_log* # 删除当前目录下 access_log 开头的文件
rm access_log\* # 删除当前目录下名字叫 access_log* 的文件
再比如我们在双引号中又出现了双引号,这时候就需要转义了,转义之后才能正常表示双引号,否则会报语法错误。比如下面的示例,引号中的 Hello World! 也是含有引号的。
print "tom said \"Hello World!\" to the crowd."
下面是一些常见的转义字符以及它们的含义。
字符串转义和正则转义
说完了转义字符,我们再来看一下正则中的转义。正则中也是使用反斜杠进行转义的。
一般来说,正则中 \d 代表的是单个数字,但如果我们想表示成 反斜杠和字母d,这时候就需要进行转义,写成 \\d,这个就表示反斜杠后面紧跟着一个字母d。
刚刚的反斜杠和d是连续出现的两个字符,如果你想表示成反斜杠或d,可以用管道符号或中括号来实现,比如 \|d 或 [\d]。
需要注意的是,如果你想用代码来测试这个,在程序中表示普通字符串的时候,我们如果要表示反斜杠,通常需要写成两个反斜杠,因为只写一个会被理解成“转义符号”,而不是反斜杠本身。
下面我给出使用 Python3 来测试的情况,你可以看一下。
>>> import re
>>> re.findall('\\|d', 'a*b+c?\d123d\') # 字符串没转义"反斜杠"
File "", line 1
re.findall('\\|d', 'a*b+c?\d123d\')
^
SyntaxError: EOL while scanning string literal
>>> re.findall('\\|d', 'a*b+c?\\d123d\\')
[]
看到这里,你内心是不是有很多问号?为什么转义了还不行呢?我们来把正则表达式部分精简一下,看看两个反斜杠在正则中是什么意思。
>>> import re
>>> re.findall('\\', 'a*b+c?\\d123d\\')
Traceback (most recent call last):
省去部分信息
re.error: bad escape (end of pattern) at position 0
我们发现,正则部分写的两个反斜杠,Python3 处理的时候会报错,认为是转义字符,即认为是单个反斜杠,如果你再进一步测试在正则中写单个反斜杠,你会发现直接报语法错误,你可以自行尝试。
那如何在正则中正确表示“反斜杠”呢?答案是写四个反斜杠。
>>> import re
>>> re.findall('\\\\', 'a*b+c?\\d123d\\')
['\\', '\\']
你可以想一下,为什么不是三个呢?后面的文本部分,也得要用四个反斜杠表示才是正确的么?到这里,你是不是发现,转义其实没那么简单。
我来给你详细解释一下这里面的过程,在程序使用过程中,从输入的字符串到正则表达式,其实有两步转换过程,分别是字符串转义和正则转义。
在正则中正确表示“反斜杠”具体的过程是这样子:我们输入的字符串,四个反斜杠 \\,经过第一步字符串转义,它代表的含义是两个反斜杠 \;这两个反斜杠再经过第二步正则转义,它就可以代表单个反斜杠 \了。
你可以用这个过程,推导一下两个和三个反斜杠的转换过程,这样你就会明白上面报错的原因了。
那在真正使用的时候,有没有更简单的方法呢?答案是有的,我们尽量使用原生字符串,在 Python 中,可以在正则前面加上小写字母 r 来表示。
>>> import re
>>> re.findall(r'\\', 'a*b+c?\\d123d\\')
['\\', '\\']
这样看起来就简单很多,因为少了上面说的第一次转换。
正则中元字符的转义
在前面的内容中,我们讲了很多元字符,相信你一定都还记得。如果现在我们要查找比如星号(*)、加号(+)、问号(?)本身,而不是元字符的功能,这时候就需要对其进行转义,直接在前面加上反斜杠就可以了。这个转义就比较简单了,下面是一个示例。
>>> import re
>>> re.findall('\+', '+')
['+']
括号的转义
在正则中方括号 [] 和 花括号 {} 只需转义开括号,但圆括号 () 两个都要转义。我在下面给了你一个比较详细的例子。
>>> import re
>>> re.findall('\(\)\[]\{}', '()[]{}')
['()[]{}']
>>> re.findall('\(\)\[\]\{\}', '()[]{}') # 方括号和花括号都转义也可以
['()[]{}']
在正则中,圆括号通常用于分组,或者将某个部分看成一个整体,如果只转义开括号或闭括号,正则会认为少了另外一半,所以会报错。
括号的转义示例,你可以参考这里:https://regex101.com/r/kJfvd6/1。
使用函数消除元字符特殊含义
我们也可以使用编程语言自带的转义函数来实现转义。下面我给出了一个在 Python里转义的例子,你可以看一下。
>>> import re
>>> re.escape('\d') # 反斜杠和字母d转义
'\\\\d'
>>> re.findall(re.escape('\d'), '\d')
['\\d']
>>> re.escape('[+]') # 中括号和加号
'\\[\\+\\]'
>>> re.findall(re.escape('[+]'), '[+]')
['[+]']
这个转义函数可以将整个文本转义,一般用于转义用户输入的内容,即把这些内容看成普通字符串去匹配,但你还是得好好注意一下,如果使用普通字符串查找能满足要求,就不要使用正则,因为它简单不容易出问题。下面是一些其他编程语言对应的转义函数,供你参考。
字符组中的转义
讲完了元字符的转义,我们现在来看看字符组中的转义。书写正则的时候,在字符组中,如果有过多的转义会导致代码可读性差。在字符组里只有三种情况需要转义,下面我来给你讲讲具体是哪三种情况。
字符组中需要转义的有三种情况
脱字符在中括号中,且在第一个位置需要转义:
>>> import re
>>> re.findall(r'[^ab]', '^ab') # 转义前代表"非"
['^']
>>> re.findall(r'[\^ab]', '^ab') # 转义后代表普通字符
['^', 'a', 'b']
中划线在中括号中,且不在首尾位置:
>>> import re
>>> re.findall(r'[a-c]', 'abc-') # 中划线在中间,代表"范围"
['a', 'b', 'c']
>>> re.findall(r'[a\-c]', 'abc-') # 中划线在中间,转义后的
['a', 'c', '-']
>>> re.findall(r'[-ac]', 'abc-') # 在开头,不需要转义
['a', 'c', '-']
>>> re.findall(r'[ac-]', 'abc-') # 在结尾,不需要转义
['a', 'c', '-']
右括号在中括号中,且不在首位:
>>> import re
>>> re.findall(r'[]ab]', ']ab') # 右括号不转义,在首位
[']', 'a', 'b']
>>> re.findall(r'[a]b]', ']ab') # 右括号不转义,不在首位
[] # 匹配不上,因为含义是 a后面跟上b]
>>> re.findall(r'[a\]b]', ']ab') # 转义后代表普通字符
[']', 'a', 'b']
字符组中其它的元字符
一般来说如果我们要想将元字符(.*+?()之类)表示成它字面上本来的意思,是需要对其进行转义的,但如果它们出现在字符组中括号里,可以不转义。这种情况,一般都是单个长度的元字符,比如点号(.)、星号(*)、加号(+)、问号(?)、左右圆括号等。它们都不再具有特殊含义,而是代表字符本身。但如果在中括号中出现 \d 或 \w 等符号时,他们还是元字符本身的含义。
>>> import re
>>> re.findall(r'[.*+?()]', '[.*+?()]') # 单个长度的元字符
['.', '*', '+', '?', '(', ')']
>>> re.findall(r'[\d]', 'd12\\') # \w,\d等在中括号中还是元字符的功能
['1', '2'] # 匹配上了数字,而不是反斜杠\和字母d
下面我来给你简单总结一下字符组中的转义情况,我们提到了三种必须转义的情况,其它情况不转义也能正常工作,但在实际操作过程中,如果遇到在中括号中使用这三个字符原本的意思,你可以都进行转义,剩下其它的元字符都不需要转义。
总结
好了,今天的内容讲完了,我来带你总结回顾一下。
正则中转义有些情况下会比较复杂,从录入的字符串文本,到最终的正则表达式,经过了字符串转义和正则转义两个步骤。元字符的转义一般在前面加反斜杠就行,方括号和花括号的转义一般转义开括号就可以,但圆括号两个都需要转义,我们可以借助编程语言中的转义函数来实现转义。另外我们也讲了字符组中三种需要转义的情况,详细的可以参考下面的脑图。
思考题
通过今天的学习,不知道你对转义掌握的怎么样了呢?再来一个例子加深一下你的理解吧,文本部分是反斜杠,n,换行,反斜杠四个部分组成。正则部分分别是1到4个反斜杠和字母n,我用Python3写了对应的示例,相应的查找过程是这样子的。
>>> import re
>>> re.findall('\n', '\\n\n\\')
['\n'] # 找到了换行符
>>> re.findall('\\n', '\\n\n\\')
['\n'] # 找到了换行符
>>> re.findall('\\\n', '\\n\n\\')
['\n'] # 找到了换行符
>>> re.findall('\\\\n', '\\n\n\\')
['\\n'] # 找到了反斜杠和字母n
例子虽然看上去简单,不过你能不能解释出这四个示例中的转义过程呢?
好了,今天的课程就结束了,希望可以帮助到你,也希望你在下方的留言区和我参与讨论,同时欢迎你把这节课分享给你的朋友或者同事,一起交流一下。
精选留言(15)
Robot 👍(26) 💬(1)google到了一篇回答,分享给大家理解课后思考:
Actually regex string specified by string literal is processed by two compilers: programming language compiler and regexp compiler:
Original Compiled Regex compiled
"\n" NL NL
"\\n" '\'+'n' NL
"\\\n" '\'+NL NL
"\\\\n" '\'+'\'+'n' '\'+'n'
https://stackoverflow.com/questions/6967204/how-is-n-and-n-interpreted-by-the-expanded-regular-expression/59192811#59192811
2020-06-24William 👍(11) 💬(3)原字符串中,共包含四个字符,第一个字符是 \,第二个是字母n,第三个是换行符,第四个是 \。
四个正则表达式的构造字符串中,第一个是换行符(正则转义后保持不变,仍然是换行符),第二个是\和n字母(正则转义后是换行符),第三个是\和换行符(正则转义后,单个换行符无意义,只剩下换行符),第四个是\\和换行符(正则转义后为一个斜杠和一个换行符)。
前三个都是找到了换行处,第四个找到了换行符。
测试JavaScript代码:
const str = '\\n\n\\'
const sources = ['\n', '\\n', '\\\n', '\\\\n']
const regs = []
sources.forEach(s => regs.push(new RegExp(s, 'g')))
regs.forEach(reg => {
console.log('[current reg] ', reg)
let once_match = reg.exec(str)
console.log('[result]', once_match)
})
2020-06-24Juntíng 👍(6) 💬(1)1、现将输入字符串正则进行字符串转义和正则转义
1. '\n' 字符转义 \n (换行符), 匹配 '\\n\n\\' 到一个换行符
2. '\\n' 字符转义 '\n'(字符\ 和 n), 正则转义 \n (换行符),匹配 '\\n\n\\' 到一个换行符
3. '\\\n' 字符转义 '\' 和 \n ('\\n' 字符\ 和 \n 换行符),正则转义 \n (换行符),匹配 '\\n\n\\' 到一个换行符
4. '\\\\n' 字符转义 '\' 、'\'、'n' ('\\n' 字符 \、\ 和 n), 正则转义 '\n' (字符 \ 和 n)
> '\\\n' 这个在转义过程中, 字符串转义后 字符'\' 和 \n (换行符) , 在正则转义时,转义一个换行符,结果还是换行符,没有意义,所以这里就没转义效果了。
2020-07-08爱乐之城 👍(4) 💬(1)'\\\n' 经过字符串转义变成反斜杠和\n,再经过正则转义变成换行符\n。前面字符串转义可以理解,但是为什么反斜杠和 \n,经过正则转义会变成 \n 呢?
2020-06-28向死而生 👍(3) 💬(2)入门课这样讲的吗?前面才一个/后面就二三四个。也不带解释一下的,我听的意义何在,还得自己找解释
2021-08-23虹炎 👍(3) 💬(1)()中内容表示匹配到的。
1,re.findall('\n', '\\n\n\\') 匹配到了 \\n(\n)\\ , '\n'就是换行,后面的\\n 表示反斜杠和n ,所以匹配到
第二个\n
2,re.findall('\\n', '\\n\n\\') 匹配到了 \\n(\n)\\ '\\n'经过字符串转义变成反斜杠和n,再经过正则转移变成换行符\n
3,re.findall('\\\n', '\\n\n\\') 匹配到了 \\n(\n)\\ '\\\n' 经过字符串转义变成反斜杠和\n,再经过正则转移变成换行符\n
4,re.findall('\\\\n', '\\n\n\\') 匹配到了 (\\n)\n\\ 经过字符串转义变成\\n,再经过正则转移变成反斜杠和n.
我的答案不知道对不对,请老师指正!
2020-06-24吕伟 👍(2) 💬(1)'\\n\n\\'输出是
\n
\
'\n'为python的换行符,
'\\n'为正则的换行符,
'\\\n'为反斜杠+python换行符,
'\\\\n'为字符\n
2020-07-18谷岳 👍(2) 💬(1)在ECMAScript中,运行结果如下:
'\\n\n\\'.match(/\n/g) 结果:["↵"]
'\\n\n\\'.match(/\\n/g) 结果:["\n"]
'\\n\n\\'.match(/\\\n/g) 结果:null
'\\n\n\\'.match(/\\\\n/g) 结果:null
我的理解是这样的:
'\\n\n\\'字符串转义结果是"\n↵\",对于正则\n自然匹配的是换行符,而\\n,正则编译成\n,所以第2题匹配结果是["\n"],而对于第3、4题,分别被正则编译成'\↵'和'\\n',无匹配返回null.
2020-07-03Jock 👍(2) 💬(1)感谢涂老师的详细讲解,\ 并没有想象的那么简单。
关于思考题的部分,前排留言已经给出了合理解释。
这里我列自己总结的一些知识点:
1. `\` 作为转义符。包括表示转义序列、转义转义字符本身,使得转义序列失效。
2. `\` 作为续行符。
3. 目前 Python 中对于非法的转义序列,会保留 `\` 在字符串中,C 语言中则是会忽略 `\`。从 Python 3.6 开始,未来,Python 中无法识别的转义序列将报语法错误。因此要表示 `\` 本身,请使用 `\\`。
4. 不管是普通字符串还是原生字符串都不能以奇数个 `\` 结尾,否则会报 `EOL` 错误。
5. 原生字符串中也可以“转义”,但是此时的“转义”有些特别,因为“转义”后 `\` 还会保留在字符串中。
6. 正则表达式字符串的转义有 2 个水平,第 1 次是 Python 解释器层面的转义,第 2 次是 `re` 模块正则引擎的转义。
7. 强烈建议用 `r` 前缀写正则表达式,省去 Python 解释器的转义。
8. `re` 模块正则引擎对于非法的转义序列直接报错 `bad escape`。
知识无穷无尽,点滴总结,聚沙成塔。以上就是分享的全部内容,如果不对之处,恳请斧正~
展开部分,我放到自己的博客,感兴趣可以去看看:https://blog.csdn.net/qq_27283619/article/details/106948855
2020-06-24ifelse 👍(1) 💬(1)正则中转义有些情况下会比较复杂,从录入的字符串文本,到最终的正则表达式,经过了字符串转义和正则转义两个步骤。--记下来
2022-11-18hai06.wang 👍(0) 💬(2)老师您好 有两个地方不太明白。我的理解如下
1、'\n'、'\\n'转移后最终是 \n 这个应该没问题
2、'\\\n' 首先经过字符串转义成 \\n ('\'+ '\n') ,正则转义后 '\' +' n'
3、'\\\\n'首先经过字符串转义成 \\n ('\'+'\'+'n'),正则转义后 '\' + 'n'
所以,针对2、3,这样理解的问题出在哪呢
2021-02-1667812 👍(0) 💬(1)题目的最后一个没理解,第一次字符串转义应该是“\\n”,那么第二次正则转义应该是“\n”,还是换行符啊
我试过利用循环打印出第一层转义
for c in "\\\\n":
print(c)
结果确实是\ \ n 呀
2020-11-13小乙哥 👍(0) 💬(1)这节课还是挺重要的,尤其对于\转义的理解。这里要建立一个认知,正则表达式也是一种语言,Java也是语言,正则表达式语言应用到Java字符串语言规范的时候,要注意在两层语言下的转义
2020-08-20🐒🐱🐭🐮🐯🐰🐶 👍(0) 💬(1)>>> import re>>>
re.findall('\n', '\\n\n\\')['\n'] # 找到了换行符>>> \n ->换行
re.findall('\\n', '\\n\n\\')['\n'] # 找到了换行符>>> \\=文本\ n=文本n ==>文本\n
re.findall('\\\n', '\\n\n\\')['\n'] # 找到了换行符>>> \\=文本\ \n=换行 ==> 文本 \ +换行
re.findall('\\\\n', '\\n\n\\')['\\n'] # 找到了反斜杠和字母n >>> \\=文本\ \\=文本\ n=文本n ==> 文本\\n
2020-07-10程序员在修行 👍(0) 💬(1)老师 我有个小疑问: 被查询的字符串在开发语言里 双反斜杠会被#字符串#转义成单反斜杠来被regex匹配吗? 四条反斜杠最后被转成单条\, python 输出 里 不太看得懂
>>> import re
>>> re.findall('\\\\', 'a*b+c?\\d123d\\')
['\\', '\\']
2020-07-04