快速入门
教程
工具和语言
示例
参考
书评
正则表达式教程
简介
目录
特殊字符
不可打印字符
正则表达式引擎内部
字符类
字符类减法
字符类交集
简写字符类
锚点
词边界
交替
可选项
重复
分组和捕获
反向引用
反向引用,第 2 部分
命名组
相对反向引用
分支重置组
自由间距和注释
Unicode
模式修饰符
原子分组
占有量词
前瞻和后顾
环视,第 2 部分
将文本排除在匹配之外
条件
平衡组
递归
子例程
无限递归
递归和量词
递归和捕获
递归和反向引用
递归和回溯
POSIX 方括号表达式
零长度匹配
继续匹配
本网站上的更多内容
简介
正则表达式快速入门
正则表达式教程
替换字符串教程
应用程序和语言
正则表达式示例
正则表达式参考
替换字符串参考
书评
可打印 PDF
关于本网站
RSS 提要和博客
RegexBuddy—Better than a regular expression tutorial!

Unicode 正则表达式

Unicode 是一种字符集,旨在定义所有语言(包括现存和已消亡语言)中的所有字符和字形。随着越来越多的软件需要支持多种语言,甚至只是任何语言,Unicode 近年来获得了极大的普及。对不同语言使用不同的字符集对于程序员和用户来说过于繁琐。

遗憾的是,在正则表达式中,Unicode 带来了自己的要求和陷阱。在本教程中讨论的正则表达式类型中,JavaXML.NET 使用基于 Unicode 的正则表达式引擎。Perl 从 5.6 版开始支持 Unicode。PCRE 可以选择编译时启用 Unicode 支持。请注意,尽管 PCRE 的名称为“Perl 兼容”,但在允许 \p 令牌方面,它的灵活性远不如 Perl。基于 PCRE 的 PHP preg 函数 在正则表达式后追加 /u 选项时支持 Unicode。Ruby 从 1.9 版开始在正则表达式中支持 Unicode 转义和属性。XRegExp 为 JavaScript 带来了对 Unicode 属性的支持。

RegexBuddy 的正则引擎 从 2.0.0 版本开始完全基于 Unicode。RegexBuddy 1.x.x 根本不支持 Unicode。 PowerGREP 从 3.0.0 版本开始使用相同的 Unicode 正则引擎。早期版本会在使用 8 位(即非 Unicode)正则引擎进行 grep 之前将 Unicode 文件转换为 ANSI。 EditPad Pro 从 6.0.0 版本开始支持 Unicode。

字符、代码点和音节或 Unicode 如何让事情变得一团糟

大多数人会认为 à 是一个字符。不幸的是,这取决于“字符”一词的含义,所以不一定正确。

本教程中讨论的所有 Unicode 正则引擎将任何单个 Unicode 代码点视为单个字符。当本教程告诉你 点匹配任何单个字符 时,这在 Unicode 术语中转化为“点匹配任何单个 Unicode 代码点”。在 Unicode 中,à 可以编码为两个代码点:U+0061 (a) 后跟 U+0300(重音符)。在这种情况下,应用于 à. 将匹配没有重音符的 a^.$ 将无法匹配,因为字符串由两个代码点组成。 ^..$ 匹配 à

Unicode 代码点 U+0300(重音符)是一个组合标记。任何不是组合标记的代码点都可以后跟任意数量的组合标记。此序列(如上文的 U+0061 U+0300)在屏幕上显示为单个音节

不幸的是,à 还可以使用单个 Unicode 代码点 U+00E0(带重音符的 a)进行编码。这种二元性的原因在于,许多历史字符集将“带重音符的 a”编码为单个字符。Unicode 的设计者认为,除了 Unicode 分离标记和基础字母的方式(这使得传统字符集不支持的任意组合成为可能)之外,与流行的传统字符集进行一对一映射会很有用。

如何匹配单个 Unicode 音节

在 Perl、PCRE、PHP、Boost、Ruby 2.0、Java 9 和 Just Great Software 应用程序中,匹配单个音节(无论它是编码为单个代码点还是使用组合标记编码为多个代码点)都很容易:只需使用 \X。你可以将 \X 视为 的 Unicode 版本。不过,有一个区别:\X 始终匹配换行符,而点不会匹配换行符,除非你启用 点匹配换行匹配模式

在 .NET、Java 8 及更早版本和 Ruby 1.9 中,你可以使用 \P{M}\p{M}*+(?>\P{M}\p{M}*) 作为相对接近的替代品。要匹配任意数量的音节,请使用 (?>\P{M}\p{M}*)+ 作为 \X+ 的替代品。

匹配特定代码点

要匹配特定 Unicode 代码点,请使用 \uFFFF,其中 FFFF 是你想要匹配的代码点的十六进制数字。你必须始终指定 4 个十六进制数字,例如 \u00E0 匹配 à,但仅当编码为单个代码点 U+00E0 时。

PerlPCREBooststd::regex 不支持 \uFFFF 语法。它们改用 \x{FFFF}。你可以省略大括号中的十六进制数字中的前导零。由于 \x 本身不是有效的正则表达式标记,因此 \x{1234} 永远不会被误认为匹配 \x 1234 次。它始终匹配 Unicode 代码点 U+1234。 \x{1234}{5678} 将尝试匹配代码点 U+1234 恰好 5678 次。

在 Java 中,正则表达式标记 \uFFFF 仅匹配指定的代码点,即使你打开了规范等价性。但是,相同的语法 \uFFFF 也用于在 Java 源代码中将 Unicode 字符插入到字符串文字中。 Pattern.compile("\u00E0") 将匹配 à 的单代码点和双代码点编码,而 Pattern.compile("\\u00E0") 仅匹配单代码点版本。请记住,在将正则表达式写为 Java 字符串文字时,必须转义反斜杠。前一个 Java 代码编译正则表达式 à,而后一个编译 \u00E0。根据你正在做什么,差异可能是显着的。

JavaScript,通过其 RegExp 类不提供任何 Unicode 支持,但支持 \uFFFF,以匹配单个 Unicode 代码点作为其字符串语法的一部分。

XML SchemaXPath 没有用于匹配 Unicode 代码点的正则表达式标记。但是,您可以轻松地使用 XML 实体(如 )将文字代码点插入正则表达式。

Unicode 类别

除了复杂性之外,Unicode 还带来了新的可能性。其中之一是每个 Unicode 字符都属于某个类别。您可以使用 \p{L} 匹配属于“字母”类别的单个字符。您可以使用 \P{L} 匹配不属于该类别的单个字符。

同样,“字符”实际上是指“Unicode 代码点”。\p{L} 匹配“字母”类别中的单个代码点。如果您的输入字符串是编码为 U+0061 U+0300 的 à,它将匹配没有重音符号的 a。如果输入是编码为 U+00E0 的 à,它将匹配带有重音符号的 à。原因是代码点 U+0061 (a) 和 U+00E0 (à) 都属于“字母”类别,而 U+0300 属于“标记”类别。

您现在应该明白为什么 \P{M}\p{M}*+ 等于 \X\P{M} 匹配不是组合标记的代码点,而 \p{M}*+ 匹配零个或多个是组合标记的代码点。要匹配包括任何变音符号的字母,请使用 \p{L}\p{M}*+。无论如何编码,这个最后一个正则表达式都将始终匹配 à所有格量词确保回溯不会导致 \P{M}\p{M}*+ 匹配没有后面跟随的组合标记的非标记,而 \X 永远不会这样做。

PCRE、PHP 和 .NET 在检查 \p 标记的大括号之间的部分时区分大小写。\p{Zs} 将匹配任何类型的空格字符,而 \p{zs} 将抛出错误。本教程中描述的所有其他正则表达式引擎将在两种情况下都匹配空格,忽略大括号之间类别的案例。不过,我建议您养成使用与我在下面的属性列表中所做相同的字母大小写组合的习惯。这将使您的正则表达式适用于所有 Unicode 正则表达式引擎。

除了标准符号 \p{L} 外,Java、Perl、PCRE、JGsoft 引擎 和 XRegExp 3 允许您使用简写 \pL。简写仅适用于单字母 Unicode 属性。 \pLl 不是 等同于 \p{Ll}。它等同于 \p{L}l,它匹配 Alàl 或任何 Unicode 字母后跟一个字面 l

Perl、XRegExp 和 JGsoft 引擎还支持长写 \p{Letter}。您可以在下面找到所有 Unicode 属性的完整列表。您可以省略下划线或改用连字符或空格。

Unicode 脚本

Unicode 标准将每个已分配的代码点(字符)放入一个脚本中。脚本是由特定人类书写系统使用的代码点组。一些脚本(如 Thai)与单一人类语言相对应。其他脚本(如 Latin)跨越多种语言。

一些语言由多个脚本组成。没有日语 Unicode 脚本。相反,Unicode 提供了 HiraganaKatakanaHanLatin 脚本,这些脚本通常用于组成日语文档。

一个特殊的脚本是 Common 脚本。此脚本包含各种字符,这些字符在各种脚本中都很常见。它包括各种标点符号、空白和杂项符号。

所有已分配的 Unicode 代码点(与 \P{Cn} 匹配的代码点)恰好属于一个 Unicode 脚本。所有未分配的 Unicode 代码点(与 \p{Cn} 匹配的代码点)根本不属于任何 Unicode 脚本。

JGsoft 引擎PerlPCREPHPRuby 1.9DelphiXRegExp 可以匹配 Unicode 脚本。以下是列表

  1. \p{Common}
  2. \p{Arabic}
  3. \p{亚美尼亚语}
  4. \p{孟加拉语}
  5. \p{注音符号}
  6. \p{盲文}
  7. \p{布希德语}
  8. \p{加拿大土著语言}
  9. \p{切罗基语}
  10. \p{西里尔语}
  11. \p{天城文}
  12. \p{埃塞俄比亚语}
  13. \p{格鲁吉亚语}
  14. \p{希腊语}
  15. \p{古吉拉特语}
  16. \p{古木基语}
  17. \p{汉语}
  18. \p{韩语}
  19. \p{哈努诺语}
  20. \p{希伯来语}
  21. \p{平假名}
  22. \p{继承}
  23. \p{卡纳达语}
  24. \p{片假名}
  25. \p{高棉语}
  26. \p{老挝语}
  27. \p{拉丁语}
  28. \p{林布语}
  29. \p{马拉雅拉姆语}
  30. \p{蒙古语}
  31. \p{缅甸语}
  32. \p{欧甘字母}
  33. \p{奥里亚语}
  34. \p{卢恩字母}
  35. \p{僧伽罗语}
  36. \p{叙利亚语}
  37. \p{塔加禄语}
  38. \p{塔格巴努瓦语}
  39. \p{泰老语}
  40. \p{泰米尔语}
  41. \p{泰卢固语}
  42. \p{塔纳语}
  43. \p{泰语}
  44. \p{藏语}
  45. \p{彝语}

Perl 和 JGsoft 风格允许你使用 \p{IsLatin} 代替 \p{Latin}。如下一节所述,“Is”语法对于区分脚本和块很有用。PCRE、PHP 和 XRegExp 不支持“Is”前缀。

Java 7 为 Unicode 脚本添加了支持。与其他风格不同,Java 7 需要“Is”前缀。

Unicode 块

Unicode 标准将 Unicode 字符映射划分为不同的块或代码点范围。每个块用于定义特定脚本的字符(如“藏语”)或属于特定组的字符(如“盲文模式”)。大多数块包括未分配的代码点,为 Unicode 标准的未来扩展保留。

请注意,Unicode 块与脚本不完全对应。块和脚本之间的一个本质区别是,块是代码点的单个连续范围,如下所示。脚本由从整个 Unicode 字符映射中获取的字符组成。块可能包括未分配的代码点(即与 \p{Cn} 匹配的代码点)。脚本绝不包括未分配的代码点。通常,如果你不确定是使用 Unicode 脚本还是 Unicode 块,请使用脚本。

例如,货币块不包括美元和日元符号。这些符号分别位于 Basic_Latin 和 Latin-1_Supplement 块中,即使它们都是货币符号,并且日元符号不是拉丁字符。这是由于历史原因,因为 ASCII 标准包括美元符号,而 ISO-8859 标准包括日元符号。您不应该盲目地根据名称使用下面列出的任何块。相反,请查看它们实际匹配的字符范围。像 RegexBuddy 这样的工具在此方面非常有用。在尝试查找所有货币符号时,Unicode 属性 \p{Sc}\p{Currency_Symbol} 将比 Unicode 块 \p{InCurrency_Symbols} 更好的选择。

  1. \p{InBasic_Latin}: U+0000–U+007F
  2. \p{InLatin-1_Supplement}: U+0080–U+00FF
  3. \p{InLatin_Extended-A}: U+0100–U+017F
  4. \p{InLatin_Extended-B}: U+0180–U+024F
  5. \p{InIPA_Extensions}: U+0250–U+02AF
  6. \p{InSpacing_Modifier_Letters}: U+02B0–U+02FF
  7. \p{InCombining_Diacritical_Marks}: U+0300–U+036F
  8. \p{InGreek_and_Coptic}: U+0370–U+03FF
  9. \p{InCyrillic}: U+0400–U+04FF
  10. \p{InCyrillic_Supplementary}: U+0500–U+052F
  11. \p{InArmenian}: U+0530–U+058F
  12. \p{InHebrew}: U+0590–U+05FF
  13. \p{InArabic}: U+0600–U+06FF
  14. \p{InSyriac}: U+0700–U+074F
  15. \p{InThaana}: U+0780–U+07BF
  16. \p{InDevanagari}: U+0900–U+097F
  17. \p{InBengali}: U+0980–U+09FF
  18. \p{InGurmukhi}: U+0A00–U+0A7F
  19. \p{InGujarati}: U+0A80–U+0AFF
  20. \p{InOriya}: U+0B00–U+0B7F
  21. \p{InTamil}: U+0B80–U+0BFF
  22. \p{InTelugu}: U+0C00–U+0C7F
  23. \p{InKannada}: U+0C80–U+0CFF
  24. \p{InMalayalam}: U+0D00–U+0D7F
  25. \p{InSinhala}: U+0D80–U+0DFF
  26. \p{InThai}: U+0E00–U+0E7F
  27. \p{InLao}: U+0E80–U+0EFF
  28. \p{InTibetan}: U+0F00–U+0FFF
  29. \p{InMyanmar}: U+1000–U+109F
  30. \p{InGeorgian}: U+10A0–U+10FF
  31. \p{InHangul_Jamo}: U+1100–U+11FF
  32. \p{InEthiopic}: U+1200–U+137F
  33. \p{InCherokee}: U+13A0–U+13FF
  34. \p{InUnified_Canadian_Aboriginal_Syllabics}: U+1400–U+167F
  35. \p{InOgham}: U+1680–U+169F
  36. \p{InRunic}: U+16A0–U+16FF
  37. \p{InTagalog}: U+1700–U+171F
  38. \p{InHanunoo}: U+1720–U+173F
  39. \p{InBuhid}: U+1740–U+175F
  40. \p{InTagbanwa}: U+1760–U+177F
  41. \p{InKhmer}: U+1780–U+17FF
  42. \p{InMongolian}: U+1800–U+18AF
  43. \p{InLimbu}: U+1900–U+194F
  44. \p{InTai_Le}: U+1950–U+197F
  45. \p{InKhmer_Symbols}: U+19E0–U+19FF
  46. \p{InPhonetic_Extensions}: U+1D00–U+1D7F
  47. \p{InLatin_Extended_Additional}: U+1E00–U+1EFF
  48. \p{InGreek_Extended}: U+1F00–U+1FFF
  49. \p{InGeneral_Punctuation}: U+2000–U+206F
  50. \p{InSuperscripts_and_Subscripts}: U+2070–U+209F
  51. \p{InCurrency_Symbols}: U+20A0–U+20CF
  52. \p{InCombining_Diacritical_Marks_for_Symbols}: U+20D0–U+20FF
  53. \p{InLetterlike_Symbols}: U+2100–U+214F
  54. \p{InNumber_Forms}: U+2150–U+218F
  55. \p{InArrows}: U+2190–U+21FF
  56. \p{InMathematical_Operators}: U+2200–U+22FF
  57. \p{InMiscellaneous_Technical}: U+2300–U+23FF
  58. \p{InControl_Pictures}: U+2400–U+243F
  59. \p{InOptical_Character_Recognition}: U+2440–U+245F
  60. \p{InEnclosed_Alphanumerics}: U+2460–U+24FF
  61. \p{InBox_Drawing}: U+2500–U+257F
  62. \p{InBlock_Elements}: U+2580–U+259F
  63. \p{InGeometric_Shapes}: U+25A0–U+25FF
  64. \p{InMiscellaneous_Symbols}: U+2600–U+26FF
  65. \p{InDingbats}: U+2700–U+27BF
  66. \p{InMiscellaneous_Mathematical_Symbols-A}: U+27C0–U+27EF
  67. \p{InSupplemental_Arrows-A}: U+27F0–U+27FF
  68. \p{InBraille_Patterns}: U+2800–U+28FF
  69. \p{InSupplemental_Arrows-B}: U+2900–U+297F
  70. \p{InMiscellaneous_Mathematical_Symbols-B}: U+2980–U+29FF
  71. \p{InSupplemental_Mathematical_Operators}: U+2A00–U+2AFF
  72. \p{InMiscellaneous_Symbols_and_Arrows}: U+2B00–U+2BFF
  73. \p{InCJK_Radicals_Supplement}: U+2E80–U+2EFF
  74. \p{InKangxi_Radicals}: U+2F00–U+2FDF
  75. \p{InIdeographic_Description_Characters}: U+2FF0–U+2FFF
  76. \p{InCJK_Symbols_and_Punctuation}: U+3000–U+303F
  77. \p{InHiragana}: U+3040–U+309F
  78. \p{InKatakana}: U+30A0–U+30FF
  79. \p{InBopomofo}: U+3100–U+312F
  80. \p{InHangul_Compatibility_Jamo}: U+3130–U+318F
  81. \p{InKanbun}: U+3190–U+319F
  82. \p{InBopomofo_Extended}: U+31A0–U+31BF
  83. \p{InKatakana_Phonetic_Extensions}: U+31F0–U+31FF
  84. \p{InEnclosed_CJK_Letters_and_Months}: U+3200–U+32FF
  85. \p{InCJK_Compatibility}: U+3300–U+33FF
  86. \p{InCJK_Unified_Ideographs_Extension_A}: U+3400–U+4DBF
  87. \p{InYijing_Hexagram_Symbols}: U+4DC0–U+4DFF
  88. \p{InCJK_Unified_Ideographs}: U+4E00–U+9FFF
  89. \p{InYi_Syllables}: U+A000–U+A48F
  90. \p{InYi_Radicals}: U+A490–U+A4CF
  91. \p{InHangul_Syllables}: U+AC00–U+D7AF
  92. \p{InHigh_Surrogates}: U+D800–U+DB7F
  93. \p{InHigh_Private_Use_Surrogates}: U+DB80–U+DBFF
  94. \p{InLow_Surrogates}: U+DC00–U+DFFF
  95. \p{InPrivate_Use_Area}: U+E000–U+F8FF
  96. \p{InCJK_Compatibility_Ideographs}: U+F900–U+FAFF
  97. \p{InAlphabetic_Presentation_Forms}: U+FB00–U+FB4F
  98. \p{InArabic_Presentation_Forms-A}: U+FB50–U+FDFF
  99. \p{InVariation_Selectors}: U+FE00–U+FE0F
  100. \p{InCombining_Half_Marks}: U+FE20–U+FE2F
  101. \p{InCJK_Compatibility_Forms}: U+FE30–U+FE4F
  102. \p{InSmall_Form_Variants}: U+FE50–U+FE6F
  103. \p{InArabic_Presentation_Forms-B}: U+FE70–U+FEFF
  104. \p{InHalfwidth_and_Fullwidth_Forms}: U+FF00–U+FFEF
  105. \p{InSpecials}: U+FFF0–U+FFFF

并非所有 Unicode 正则表达式引擎都使用相同的语法来匹配 Unicode 块。 JavaRuby 2.0XRegExp 使用如上所列的 \p{InBlock} 语法。 .NETXML 则使用 \p{IsBlock}PerlJGsoft 风格 支持这两种表示法。如果您使用的正则表达式引擎支持“In”表示法,我建议您使用它。“In”只能用于 Unicode 块,而“Is”还可以用于 Unicode 属性和脚本,具体取决于您使用的正则表达式风格。通过使用“In”,很明显您匹配的是一个块,而不是一个类似命名的属性或脚本。

在 .NET 和 XML 中,您必须省略下划线,但保留块名称中的连字符。例如,使用 \p{IsLatinExtended-A} 而不是 \p{InLatin_Extended-A}。在 Java 中,您必须省略连字符。.NET 和 XML 还区分名称的大小写,而 Perl、Ruby 和 JGsoft 风格则不区分大小写。Java 4 区分大小写。Java 5 及更高版本对“Is”前缀区分大小写,但对块名称本身不区分大小写。

所有正则表达式引擎中块的实际名称都是相同的。块名称在 Unicode 标准中定义。PCRE 和 PHP 不支持 Unicode 块,即使它们支持 Unicode 脚本。

您需要担心不同的编码吗?

虽然您应该始终牢记由重音字符的不同编码方式带来的陷阱,但您并不总是需要担心它们。如果您知道您的输入字符串和正则表达式使用相同的样式,那么您根本不必担心。此过程称为 Unicode 规范化。所有具有原生 Unicode 支持的编程语言,如 Java、C# 和 VB.NET,都具有用于规范化字符串的库例程。如果您在尝试匹配之前规范化主题和正则表达式,就不会有任何不一致。

如果您使用 Java,则可以将 CANON_EQ 标志作为第二个参数传递给 Pattern.compile()。这会指示 Java regex 引擎将规范相等的字符视为相同字符。编码为 U+00E0 的 regex à 匹配编码为 U+0061 U+0300 的 à,反之亦然。当前没有其他 regex 引擎在匹配时支持规范相等性。

如果您在键盘上键入 à 键,据我所知,所有文字处理器都会将代码点 U+00E0 插入到文件中。因此,如果您使用自己键入的文本,您自己键入的任何 regex 都将以相同的方式匹配。

最后,如果您使用 PowerGREP 搜索使用传统 Windows(通常称为“ANSI”)或 ISO-8859 代码页编码的文本文件,PowerGREP 始终使用一对一替换。由于所有 Windows 或 ISO-8859 代码页都将重音字符编码为单个代码点,因此在将文件转换为 Unicode 时,几乎所有软件都为每个字符使用单个 Unicode 代码点。