正则基础之——神奇的转义

1        概述

这或许会是一个让人迷惑,甚至感到混乱的话题,但也正因为如此,才有了讨论的必要。

在正则中,一些具有特殊意义的字符,或是字符序列,被称作元字符,如“?”表示被修饰的子表达式匹配0次或1次,“(?i)”表示忽略大小写的匹配模式等等。而当这些元字符被要求匹配其本身时,就要进行转义处理了。

不同的语言或应用场景下,正则定义方式、元字符出现的位置不同,转义的方式也是林林总总,不一而同。

2       .NET正则中的字符转义

2.1     .NET正则中的转义符

绝大多数语言中,“\”都被作为转义符,用来转义一些具有特殊意义的字符或字符序列,比如“\n”表示换行,“\t”表示水平制表符等。而这样的转义,应用到正则中,又会有一些意想不到的变化。

话题由C#中一个正则问题引出

string[] test = new string[]{"\\", "\\\\"};

Regex reg = new Regex("^\\\\$");

foreach (string s in test)

{

     richTextBox2.Text += "源字符串: " + s.PadRight(5, ' ') + "匹配结果: " + reg.IsMatch(s) + "\n";

}

/*--------输出--------

源字符串: \    匹配结果: True

源字符串: \\   匹配结果: False

*/

对于这个结果,或许有人会感到迷惑,字符串中的“\\”不是代表一个经过转义的“\”字符吗?而“\\\\”不就应该代表两个经过转义的“\”字符吗?那么上面正则匹配的结果应该是第一个为False,第二个为True才对啊?

对于这一问题,直接解释或许不太容易理解,还是换种方式来解释吧。

比如要匹配的字符是这样的

string test = "(";

那么正则如何写呢?因为“(”在正则中是有特殊意义的,所以写正则时必须对它进行转义,也就是“\(”,而在字符串中,要使用“\\ 来表示“\”本身,也就是

Regex reg = new Regex("^\\($");

这个如果理解了,那再把“(”换回“\”,同样道理,在字符串中,要使用“\\ 来表示“\”本身,也就是

Regex reg = new Regex("^\\\\$");

通过这样的分析,可以看出,其实在以字符串形式声明的正则中,“\\\\”匹配的实际上就是单独的一个“\”字符。总结一下它们之间的关系:

输出到控制台或界面的字符串:\

程序中声明的字符串:string test = "\\";

程序中声明的正则:Regex reg = new Regex("^\\\\$");

这样解释是不是已经可以理解了,那么是不是感觉这样很笨拙?是的,在程序中以字符串形式声明的正则,涉及到转义符时就是这样笨拙的。

所以在C#中,还提供了另一种字符串声明方式,在字符串前加个“@”,就可以忽略转义。

string[] test = new string[] { @"\", @"\\" };

Regex reg = new Regex(@"^\\$");

foreach (string s in test)

{

    richTextBox2.Text += "源字符串: " + s.PadRight(5, ' ') + "匹配结果: " + reg.IsMatch(s) + "\n";

}

/*--------输出--------

源字符串: \    匹配结果: True

源字符串: \\   匹配结果: False

*/

这样就简洁多了,也符合通常的理解。

但同时也带来另一个问题,就是双引号的转义处理。在普通的字符串声明中,可以用“\””对双引号进行转义。

string test = "<a href=\"www.test.com\">only a test</a>";

但是在字符串前加了“@”后,“\”会被识别为“\”字符本身,这样就不能用“\””对双引号进行转义了,需要用“”””对双引号进行转义。

string test = @"<a href=""www.test.com"">only a test</a>";

而在VB.NET中,正则的定义只有一种形式,与C#中加了“@”后的定义方式是一致的。

Dim test As String() = New String() {"\", "\\"}

Dim reg As Regex = New Regex("^\\$")

For Each s As String In test

    RichTextBox2.Text += "源字符串:" & s.PadRight(5, " "c) & "匹配结果:" & reg.IsMatch(s) & vbCrLf

Next

'--------输出--------

'源字符串:\    匹配结果:True

'源字符串:\\   匹配结果:False

'--------------------

2.2     .NET正则中需要转义的元字符

MSDN中,以下字符作为正则中的元字符,在匹配其本身时,需要对其进行转义

. $ ^ { [ ( | ) * + ? \

但实际应用中,还要根据实际情况来判断,以上字符可能不需要转义,也可能不止以上字符需要转义。

在正常的正则书写过程中,以上字符的转义通常都能被编写人员正常处理,但是在动态生成正则时,就需要格外的注意,否则变量中包含元字符时,动态生成的正则在编译时可能会抛异常。好在.NET中提供了Regex.Escape方法来处理这一问题。比如根据动态获取的id来提取相应的div标签内容。

string id = Regex.Escape(textBox1.Text);

Regex reg = new Regex(@"(?is)<div(?:(?!id=).)*id=(['""]?)" + id  + @"\1[^>]*>(?><div[^>]*>(?<o>)|</div>(?<-o>)|(?:(?!</?div\b).)*)* (?(o)(?!))</div>");

如果不做转义处理,那么动态获取的id如果为“abc(def”这种形式,程序运行过程中就会抛出异常了。

2.3     .NET正则中字符组的转义

在字符组[]中,元字符通常是不需要转义的,甚至于“[”也是不需要转义的。

string test = @"the test string:  . $ ^ { [ ( | ) * + ? \";

Regex reg = new Regex(@"[.$^{[(|)*+?\\]");

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "\n";

}

/*--------输出--------

.

$

^

{

[

(

|

)

*

+

?

\

*/

但是在正则书写时,字符组中的“[”还是建议使用“\[”对其转义的,正则本身就已经是非常抽象,可读性很低的了,如果在字符组中再掺杂进这样不经转义的“[”,会使得可读性更差。而且在出现不正确的嵌套时,可能会导致正则编译异常,以下正则在编译时就会抛异常的。

Regex reg = new Regex(@"[.$^{[(]|)*+?\\]");

然而,.NET的字符组中,是支持集合减法的,在这种正常语法形式下,是允许字符组嵌套的。

string test = @"abcdefghijklmnopqrstuvwxyz";

Regex reg = new Regex(@"[a-z-[aeiou]]+");

MatchCollection mc = reg.Matches(test);

foreach (Match m in mc)

{

     richTextBox2.Text += m.Value + "\n";

}

/*--------输出--------

bcd

fgh

jklmn

pqrst

vwxyz

*/

这种用法可读性很差,应用也很少见,即使有这种需求也可以通过其它方式实现,了解一下即可,不必深究。

话题再回到转义上,字符组中必须转义的只有“\”,而“[”和“]”出现在字符组中时,也是建议一定做转义处理的。另外有两个字符“^”和“-”,出现在字符组中特定位置时,如果要匹配其本身,也是需要转义的。

^”出现在字符组开始位置,表示排除型字符组,“[^Char]”也就是匹配除字符组中包含的字符之外的任意一个字符,比如“[^0-9]”表示除数字外的任意一个字符。所以在字符组中,要匹配“^”字符本身,要么不放在字符组开始位置,要么用“\^”进行转义。

Regex reg1 = new Regex(@"[0-9^]");

Regex reg2 = new Regex(@"[\^0-9]");

这两种方式都表达匹配任意一个数字或普通字符“^”。

至于“-”在字符组中特殊性,举一个例子。

string test = @"$";

Regex reg = new Regex(@"[#-*%&]");

richTextBox2.Text = "匹配结果:" + reg.IsMatch(test);

/*--------输出--------

匹配结果:True

*/

正则表达式中明明没有“$”,为什么匹配结果会是“True”呢?

[]支持用连字符“-”连接两个字符,来表示一个字符范围。需要注意的是,“-”前后的两个字符是有顺序的,在使用相同的编码时,后面的字符码位应大于或等于前面字符的码位。

for (int i = '#'; i <= '*'; i++)

{

     richTextBox2.Text += (char)i + "\n";

}

/*--------输出--------

#

$

%

&

'

(

)

*

*/

由于“#”和“*”符合要求,“[#-*]”可以表示一个字符范围,其中就包含了字符“$”,所以上面的正则是可以匹配“$”的,如果只是把“-”当作一个普通字符处理,那么要么换个位置,要么把“-”转义。

Regex reg1 = new Regex(@"[#*%&-]");

Regex reg2 = new Regex(@"[#\-*%&]");

这两种方式都表示匹配字符组中列举的字符中的任意一个。

在字符组中,还有一个比较特殊的转义字符,“\b”出现在正则表达式中一般位置时,表示单词边界,也就是一侧为组成单词的字符,另一侧不是;而当“\b”出现在字符组中时,表示的是退格符,与普通字符串中出现的“\b”意义是一样的。

同样的,还有一个容易被忽视,而且经常被忽视的转义符“|”,当“|”出现在正则表达式中一般位置时,表示左右两侧“或”的关系;而当“|”出现在字符组中时,它仅仅表示“|”字符本身,没有任何特殊意义,所以如果不是要匹配“|”本身,而试图在字符组中使用“|”时,是错误的。比如正则表达式“[a|b]”表示的是“a”、“b”、“|”中的任意一个,而不是“a”或“b”。

2.4     .NET正则应用中不可见字符转义处理

对于一些不可见字符,要在字符串中表示时,需要用转义字符,比较常见的有“\r”、“\n”、“\t”等等,而这些字符在正则中应用,就变得有些神奇了,先看一段代码。

string test = "one line. \n another line.";

List<Regex> list = new List<Regex>();

list.Add(new Regex("\n"));

list.Add(new Regex("\\n"));

list.Add(new Regex(@"\n"));

list.Add(new Regex(@"\\n"));

foreach (Regex reg in list)

{

    richTextBox2.Text += "正则表达式:" + reg.ToString();

    MatchCollection mc = reg.Matches(test);

    foreach (Match m in mc)

    {

        richTextBox2.Text += "   匹配内容:" + m.Value + "   匹配起始位置:" + m.Index + "   匹配长度:" + m.Length;

    }

    richTextBox2.Text += "   匹配总数:" + reg.Matches(test).Count + "\n----------------\n";

}

/*--------输出--------

正则表达式:

   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式:\n   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式:\n   匹配内容:

   匹配起始位置:10   匹配长度:1   匹配总数:1

----------------

正则表达式:\\n   匹配总数:0

----------------

*/

可以看到,前三种写法,输出的正则虽不同,但执行结果却是完全相同的,只有最后一种是没有匹配的。

正则表达式一Regex("\n"),其实就是以普通字符串形式来声明正则的,与用Regex("a")来匹配字符“a”是同样的道理,是不经过正则引擎转义的。

正则表达式二Regex("\\n"),是以正则表达式形式来声明正则的,正如正则中的“\\\\就等同于字符串中的“\\”一样,正则中的“\\n”就等同于字符串中的“\n”,是经过正则引擎转义的。

正则表达式三Regex(@"\n"),与正则表达式二等价,是字符串前加“@”的写法。

正则表达式四Regex(@"\\n"),其实这个表示的是字符“\”后面跟一个字符“n”,是两个字符,这个在源字符串中自然是找不到匹配项的。

这里需要特别注意的还是\b”,不同的声明方式,“\b”的意义是不同的。

string test = "one line. \n another line.";

List<Regex> list = new List<Regex>();

list.Add(new Regex("line\b"));

list.Add(new Regex("line\\b"));

list.Add(new Regex(@"line\b"));

list.Add(new Regex(@"line\\b"));

foreach (Regex reg in list)

{

     richTextBox2.Text += "正则表达式:" + reg.ToString() + "\n";

     MatchCollection mc = reg.Matches(test);

     foreach (Match m in mc)

     {

          richTextBox2.Text += "匹配内容:" + m.Value + "   匹配起始位置:" + m.Index + "   匹配长度:" + m.Length + "\n";

     }

     richTextBox2.Text += "匹配总数:" + reg.Matches(test).Count + "\n----------------\n";

}

/*--------输出--------

正则表达式:line_

匹配总数:0

----------------

正则表达式:line\b

匹配内容:line   匹配起始位置:4   匹配长度:4

匹配内容:line   匹配起始位置:20   匹配长度:4

匹配总数:2

----------------

正则表达式:line\b

匹配内容:line   匹配起始位置:4   匹配长度:4

匹配内容:line   匹配起始位置:20   匹配长度:4

匹配总数:2

----------------

正则表达式:line\\b

匹配总数:0

----------------

*/

正则表达式一Regex("line\b"),这里的“\b”是退格符,是不经过正则引擎转义的。源字符串中是没有的,所以匹配结果为0

正则表达式二Regex("line\\b"),是以正则表达式形式来声明正则的,这里的“\\b”是单词边界,是经过正则引擎转义的。

正则表达式三Regex(@"line\b"),与正则表达式二等价,指单词边界。

正则表达式四Regex(@"line\\b"),其实这个表示的是字符“\”后面跟一个字符“b”,是两个字符,这个在源字符串中自然是找不到匹配项的。

2.5     .NET正则应用中其它转义处理

.NET正则应用中还有一些其它转义方式,虽然用得不多,但也顺便提一下吧。

需求:把字符串中“<”和“>”之间的数字前加上“$

string test = "one test <123>, another test <321>";

Regex reg = new Regex(@"<(\d+)>");

string result = reg.Replace(test, "<$$1>");

richTextBox2.Text = result;

/*--------输出--------

one test <$1>, another test <$1>

*/

也许你会惊奇的发现,替换结果不是在数字前加了“$”,而是将所有数字都替换为“$1”了。

为什么会这样呢,这是因为在替换结构中,“$”是有特殊意义的,在它后面接数字,表示对对应编号捕获组匹配结果的引用,而有些情况下,需要在替换结果中出现“$”字符本身,但它后面又跟了数字,这时候就需要用“$$”对它进行转义了。而上面这个例子却恰恰是由于这种转义效果导致出现了异常结果,要规避这一问题,可以使替换结果中不出现对捕获组的引用。

string test = "one test <123>, another test <321>";

Regex reg = new Regex(@"(?<=<)(?=\d+>)");

string result = reg.Replace(test, "$");

richTextBox2.Text = result;

/*--------输出--------

one test <$123>, another test <$321>

*/

3       JavaScriptJava中的转义符

JavaScriptJava中正则的转义符处理,以字符串形式声明时,基本上都是与.NET中一致的,简单的介绍一下。

JavaScript中,以字符串形式声明正则,与C#中的表现是一样的,同样会显得很笨拙。

<script type="text/javascript">

    var data = ["\\", "\\\\"];

    var reg = new RegExp("^\\\\$", "");

    for(var i=0;i<data.length;i++)

    {

        document.write("源字符串:" + data[i]  + "   匹配结果:" + reg.test(data[i]) + "<br />");

    }

</script>

/*--------输出--------

源字符串:\ 匹配结果:true

源字符串:\\ 匹配结果:false

*/

JavaScript中虽然没有提供C#中这种“@”方式的字符串声明方式,但提供了另一种正则表达式的专有声明方式。

<script type="text/javascript">

    var data = ["\\", "\\\\"];

    var reg = /^\\$/;

    for(var i=0;i<data.length;i++)

    {

        document.write("源字符串:" + data[i]  + "   匹配结果:" + reg.test(data[i]) + "<br />");

    }

</script>

/*--------输出--------

源字符串:\ 匹配结果:true

源字符串:\\ 匹配结果:false

*/

JavaScript

var reg = /Expression/igm;

这种声明方式,一样可以简化含有转义符的正则。

当然,以这种形式声明正则时,“/”自然也就成为了元字符,正则中出现这一字符时,必须进行转义处理。比如匹配链接中域名的正则

var reg = /http:\/\/:([^\/]+)/ig;

很不幸的是,在Java中,目前只提供了一种正则声明方式,也就是字符串形式的声明方式

String test[] = new String[]{"\\", "\\\\" };

String reg = "^\\\\$";

for(int i=0;i<test.length ;i++)

{

  System.out.println("源字符串:" + test[i] + "   匹配结果:" + Pattern.compile(reg).matcher(test[i]).find());

}

/*--------输出--------

源字符串:\   匹配结果:true

源字符串:\\   匹配结果:false

*/

只能期待Java的后续版本能提供这方面的优化了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296478.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

没有女朋友,可能是因为你数学不好

全世界只有3.14 % 的人关注了爆炸吧知识孔子和耶稣曾说过&#xff1a;初恋无限好。回想起青涩的大学时光&#xff0c;告别了高中时代紧张的学习氛围和父母、老师的谆谆告诫&#xff0c;爱情也不再是伊甸园里的禁果。关于爱情的开展和维系&#xff0c;在Levinger&#xff08;198…

C#10,带来了Date和Time类型

C#10引入了日期DateOnly&#xff0c;时间TimeOnly&#xff1a;//从DateTime转换 Console.WriteLine(DateOnly.FromDateTime(DateTime.Now)); //从字会串转换 Console.WriteLine(DateOnly.Parse("2021-10-23")); //从0001-01-01到现在的天数 Console.WriteLine(DateOn…

mysql插入时间区间_mybatis插入数据时返回主键以及MySQL根据时间区间查询问题总结...

最近做项目的过程中&#xff0c;在数据库方面遇到了两个问题&#xff0c;一是在插入一条数据的时候需要将该条数据的主键返回、二是根据时间区间进行查询时某一天的数据查询不到&#xff0c;在此总结记录一下。1、如何在插入一条数据的同时将主键返回在实体类的映射文件 "…

DML语言(重点)———insert

数据库的意义&#xff1a;数据存储&#xff0c;数据管理 DML语言&#xff1a;数据操作语言 1.insert:添加 2.update:修改 3.delete&#xff1a;删除 DML-添加数据 1.给指定字段添加数据 INSERT INTO 表名(字段名1&#xff0c;字段名2&#xff0c;……) VALUES(值1&#…

android下升级软件介绍

编译android&#xff1a; 生成&#xff1a;system.img,ramdisk.img,userdata.img映像文件。 ramdisk.img是emulator的文件系统 system.img包括了主要的包、库等文件 userdata.img包括了一些用户数据 emulator加载这3个映像文件后&#xff0c;会把 system和 userdata分别加载到 …

near far pointer

near指针的长度是16位的&#xff0c;所以可指向的地址范围是64K字节&#xff0c;通常说near指针的寻址范围是64K。far指针的长度是32位&#xff0c;含有一个16位的基地址和16位的偏移量&#xff0c;将基地址乘以16后再与偏移量相加&#xff0c;&#xff08;所以实际上far指针是…

数据可视化----我在寻找一款类似vfp或是access这样自带可视化风格的数据库或是键盘数据库...

我在寻找一款类似vfp或是access这样自带可视化风格的数据库或是键盘数据库影响redis,mongodb今后发展的我也认为是一些可视化工具的支持http://blog.xiqiao.info/tag/data-visualization

AgileConfig 1.5 发布 - 支持多环境配置

AgileConfig 从发布到现在&#xff0c;收到不少同学的 issue 说需要多环境的支持。也就是一个应用在不同的环境下可以配置不同的配置项。这是一个非常有用的功能&#xff0c;就跟我们开发的时候会设置多个 appsettings.json 文件一样&#xff0c;比如 appsettings.development.…

知乎高赞:哪些事坚持做3个月就会有巨大改变?

全世界只有3.14 % 的人关注了爆炸吧知识知乎上有个高赞问题&#xff1a;有哪些书看完后&#xff0c;会让人后悔没早看到&#xff1f;答案各有不一、包罗万象。但有一点&#xff0c;大家达成了共识&#xff1a;要多读书。人生漫长&#xff0c;而我们都不会是一成不变的&#xff…

clover 主题_Clover主题更换

小白前言今天,黑果小白来教大家更换自己clover引导的 主题 ,相信大家看了这么久的主题 ,应该也厌倦了吧,换一个主题,增加一下新鲜度,废话不多说,上教程!更换前的准备要想更换主题&#xff0c;你就必须得有主题可以更换&#xff0c;没有主题&#xff0c;神马都是浮云&#xff01…

服务器教程笔记

1.分类。入门级&#xff08;几台&#xff09;&#xff0c;工作组&#xff08;50台左右&#xff09;&#xff0c;部门级&#xff08;100&#xff09;&#xff0c;企业级&#xff08;少数几个厂家能生产&#xff09;。 2 服务器设置host 然后再apache也设置&#xff0c;这样就可以…

IOS使用Auto Layout中的VFL适配

做登录页面,之前做都是用frame做,今天想着用Auto Layout中的VFL来做。觉得做的效果还是可以的(自恋一下下)。 首先看下效果图和标记图 自己在做的过程中也遇到了好多问题,不过也一个一个的自己解决了 1.子视图居中的问题 上一博客我也写了,由于指定了视图的宽度高度,想让视图居…

C#中字符“.NET研究”串的内存分配与驻留池

刚开始学习C#的时候&#xff0c;就听说CLR对于String类有一种特别的内存管理机制&#xff1a;有时候&#xff0c;明明声明了两个String类的对象&#xff0c;但是他们偏偏却指向同一个实例。如下&#xff1a; String s1 "Hello";String s2 "Hello"; //s2和s…

如何在业务层实现响应缓存

前言上次&#xff0c;我们介绍了应该在业务层实现管道模式响应缓存是ASP.NET Core中很重要的功能&#xff0c;它可以存储响应&#xff0c;并提供来自缓存的响应&#xff0c;以便提高程序性能。响应缓存通常是通过Middleware实现的&#xff1a;public static class ResponseCach…

mysql 5.5.46_MySQL 5.5.46源码安装

环境信息准备操作系统是centos 6.5 yum install gcc gcc-c cmake ncurses-devel安装mysql解压 tar -xzvf mysql-5.5.46.tar.gz -Cbuild编译 由于使用了cmake进行编译cd build/mysql-5.5.46cmake . \-DCMAKE_INSTALL_PREFIX$HOME/local/mysql-5.5.46 \ #指定安装目录-DWITH_INNO…

《Effective.Enterprise.Java中文版》知识点摘要

《Effective.Enterprise.Java中文版》本书最重要的部分是&#xff1a;理解企业级计算技术中的常规问题和使用企业级JAVA平台技术来处理这些问题。. 语言和API也许会发生变化&#xff0c;但是你将会理解&#xff1a;构建良好架构所要考虑的问题&#xff1b;有那些通信方式可供选…

左手菲尔兹右手突破奖,这个中国女婿其实是英国贵族?拿到300万奖金后他这样说……...

全世界只有3.14 % 的人关注了爆炸吧知识如果说科学界里高大上的奖项&#xff0c;你还是只能想到诺贝尔&#xff0c;菲尔兹&#xff0c;那可能真的是out了。毕竟现在都0202年了。少年&#xff0c;突破奖了解一下&#xff1f;作为有马云&#xff0c;马化腾&#xff0c;还有俄罗斯…

理财平台架构分析

一个理财平台可以从不同的维度来看。对于一个消费者来说&#xff0c;最宏观的看法&#xff0c;P2P公司的理财平台相当于一个中介&#xff0c;一边用于对接用户&#xff0c;一边用于对接产品提供商。这个中介系统负责用户和产品提供者之间的交互。对于一个P2P理财公司来说&#…

Linux下添加普通帐号

Linux下系统管理员其中一个很重要的职能就是帐号管理.一般来说很多比较好的发行版本都会不建议用户使用root用户登录系统的,因为root用户的权限实在太大了,一不小心就可能破坏了系统.因此我们一般使用普通帐号登陆系统,而要使用普通帐号登陆就必须拥有普通帐号.本文将介绍几种添…

mysql数据压缩存储_压缩文本,然后存储在mysql数据库中

I am developing a web application using php and MySQL. I am compressing the text using gzcompress() php function before storing in MySQL database / storing compressed form of text in database. My Question is that is this OK? to store compressed form? Or …