使用正则表达式匹配UTF-8编码下的任何中文字符

47
例如,我想匹配一个由mn个汉字组成的字符串,那么我可以使用以下代码:
[single Chinese character regular expression]{m,n}

是否有一个单个中文字符的正则表达式,可以匹配任何存在的中文字符?


1
请至少提供您正在使用的正则表达式引擎的信息。 - Lily Ballard
@KevinBallard 我不确定我是在使用哪个引擎。我的知道的是,我在flex(词法分析器)中使用了正则表达式功能。 - xiaohan2012
可能是如何使flex(词法扫描器)读取UTF-8字符输入?的重复问题。 - Thomas Dickey
Flex不会做这件事;认为它会做这件事的答案并没有回答问题。 - Thomas Dickey
8个回答

48

匹配中文(或CJK字符)的正则表达式是

\p{script=Han}

可以简写为

\p{Han}

这假设你的正则表达式编译器符合要求RL1.2从UTS#18 Unicode 正则表达式属性。 Perl和Java 7都符合该规范,但许多其他编译器则不符合。


2
@xiaohan2012:我认为Flex根本不支持Unicode。 - Lily Ballard
3
有没有适用于C#的变体? - tofutim
这在Coda 2的查找和替换中完美运行。谢谢! - Jake
6
在Java-8中它返回“未知字符属性名称{Han}”,有什么问题吗? - Andremoniy
也许可以考虑使用RE/flex作为Flex的替代方案?它能够很好地处理Unicode,并且使用相同的Flex语法。我对Flex缺乏进展感到不满,因此我创建了一个新版本(RE/flex),它能够理解现代Unicode和字符编码。 - Dr. Alex RE
显示剩余4条评论

7
在Java中,
\p{InCJK_UNIFIED_IDEOGRAPHS}{1,3}

请注意,这仅在U+4E00至U+9FFF范围内查找字符。它不能找到所有现有的汉字。 - martin
1
该问题标记为 Flex 用于 C 和 C++ 的词法分析器,该分析器不支持 \p{C} 字符块。 - Dr. Alex RE

6

在C#中

new Regex(@"\p{IsCJKUnifiedIdeographs}")

这里是从Microsoft文档中找到的。

以下是维基百科提供的更多信息: CJK统一汉字

CJK统一汉字基本块(4E00–9FFF)包含20,976个基本汉字,范围为U+4E00至U+9FEF。该块不仅包括中文书写系统中使用的字符,还包括日本书写系统中使用的汉字和韩国正在逐渐减少使用的汉字。该块中的许多字符在所有三个书写系统中都有使用,而其他字符则只在其中一两个书写系统中使用。汉字也用于越南的Nôm文字(现已过时)。


1
谢谢你的回答!为了帮助改进你的帖子,请考虑添加文档链接,或添加解释以帮助说明这是做什么的。 - Kevin

2

有没有一个单个汉字的正则表达式,可以匹配任何已知的汉字?

建议

为了在支持Flex的词法分析器中匹配包含汉字和其他Unicode代码点的模式,您可以使用RE/flex词法分析器进行C++编程,它与Flex向后兼容。RE/flex支持Unicode,并且可以与Bison一起构建词法分析器和解析器。

您可以在RE/flex规范中编写Unicode模式(以及UTF-8正则表达式),例如:

%option flex unicode
%%
[肖晗]   { printf ("xiaohan/2\n"); }
%%

使用全局选项%option unicode来启用Unicode。您还可以使用本地修饰符(?u:)将Unicode限定为单个模式(因此其他所有内容仍然是ASCII / 8位,如Flex):

%option flex
%%
(?u:[肖晗])   { printf ("xiaohan/2\n"); }
(?u:\p{Han})  { printf ("Han character %s\n", yytext); }
.             { printf ("8-bit character %d\n", yytext[0]); }
%%

选项 flex 启用了 Flex 兼容性,因此您可以使用 yytextyylengECHO 等。如果没有使用 flex 选项,则 RE/flex 期望 Lexer 方法调用:text()(或者对于 std::stringstd::wstringstr()wstr())、size()(或者对于宽字符长度,wsize())和 echo()。我认为 RE/flex 的方法调用更加简洁,并包括宽字符操作。

背景

在普通的 Flex 中,我最终定义了丑陋的 UTF-8 模式来捕获 ASCII 字母和 UTF-8 编码的字母,以支持 Unicode 标识符 id 的编译器项目。

digit           [0-9]
alpha           ([a-zA-Z_\xA8\xAA\xAD\xAF\xB2\xB5\xB7\xB8\xB9\xBA\xBC\xBD\xBE]|[\xC0-\xFF][\x80-\xBF]*|\\u([0-9a-fA-F]{4}))
id              ({alpha})({alpha}|{digit})*            

alpha 模式支持 ASCII 字母、下划线和用于标识符的Unicode代码点 (\p{L} 等)。该模式允许更多的Unicode代码点,以使该模式的大小可管理,因此在一些情况下允许UTF-8过长字符,但这些字符并不是有效的UTF-8,这种方式会导致一些准确性缺失和安全问题,请谨慎采用。建议使用 Unicode-capable 的扫描器生成器,例如RE/flex

安全性

直接在Flex模式中使用UTF-8时存在以下几个问题:

  1. 在 Flex 中编码您自己的 UTF-8 模式以匹配任何 Unicode 字符可能容易出错。应该将模式限制为仅包含有效 Unicode 范围内的字符。Unicode 代码点覆盖范围从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF。范围 U+D800 到 U+DFFF 保留用于 UTF-16 替代对,并且为无效的代码点。当使用工具将 Unicode 范围转换为 UTF-8 时,请确保排除无效的代码点。

  2. 模式应该拒绝过长和其他无效字节序列。无效的 UTF-8 应该不能被静默接受。

  3. 为了捕获词法输入错误,您的词法分析器需要特殊的. ( 点) ,它匹配有效和无效的Unicode,包括UTF-8超出限定范围和无效字节序列,以便产生一个拒绝输入的错误消息。如果您使用点作为“捕获所有其他”以产生错误消息,但是您的点不匹配无效 Unicode,则您的词法分析器会挂起(“扫描器已卡住”)或者由 Flex 的“默认规则”在输出中回显垃圾字符。

  4. 您的扫描器应该识别输入中的UTF BOM (Unicode 字节顺序标记) 并切换到 UTF-8、UTF-16 (LE 或 BE) 或 UTF-32 (LE 或 BE)。

  5. 如您所指出的,例如[unicode characters]这样的模式在 Flex 中根本不起作用,因为括号列表中的 UTF-8 字符是多字节字符,可以匹配每个单字节字符但无法匹配 UTF-8 字符。

请参考 RE/flex 用户指南中关于无效的 UTF 编码的说明


1
对于大多数编程语言,匹配99.9%以上的中文字符的正则表达式将是:

\u4E00-\u9FFF

适用于:Python、现代JavaScript、Golang、Rust 但不适用于PHP

如果您的语言不支持其他答案中的符号表示法,如{Han}/{script=Han}/{IsCJKUnifiedIdeographs},则此方法非常有用。

NB:这对应于CJK统一汉字,包括韩语、日语和越南语等其他语言。


1
对于接受(或需要)Unicode编码文本的输入,[\u4E00-\u9FFF]等同于[一-鿿] - remcycles
这也适用于罗马字符 - undefined

0
在Java 7及以上版本中,格式应为:“\p{IsHan}”。

1
实际上,编辑历史记录显示您也写了 InHan,@Robert 只是添加了格式,使表达式呈现为等宽字体。 - Zoltán
提示:您可以选择自己编辑以纠正错误。;-) - Robert
问题并不是要求如何在Java中实现,而是标记为“flex-lexer”。 - user743382

-1

刚刚解决了一个类似的问题,

当你有太多的内容需要匹配时,最好使用 negated-set 并声明你不想匹配的内容,例如:

除数字外的所有内容:^[^0-9]*$

第二个 ^ 将实现否定。


1
如果采用这种方法来回答OP的问题,结果会怎样呢?我认为这比已经提供的答案更加繁琐。 - Philippe-André Lorin

-1

就像这样:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    compile, err := regexp.Compile("\\p{Han}") // match one any Chinese character
    if err != nil {
        return
    }
    str := compile.FindString("hello 世界")
    fmt.Println(str) // output: 世
}

1
请阅读[答案]并[编辑]您的答案,以包含有关此代码实际解决问题的说明。请记住,您不仅要解决问题,还要教育OP和任何未来读者。 - Adriaan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接