2023年政策修订增补工作正在进行中,欢迎参与!
  • Moegirl.ICU:萌娘百科流亡社群 581077156(QQ),欢迎对萌娘百科运营感到失望的编辑者加入
  • Moegirl.ICU:账号认领正在试运行,有意者请参照账号认领流程

Help:正则表达式

萌娘百科,万物皆可萌的百科全书!转载请标注来源页面的网页链接,并声明引自萌娘百科。内容不可商用。
跳转到导航 跳转到搜索
Commons-emblem-notice.svg
这个页面“Help:正则表达式”是萌娘百科的帮助文档
  • 本文用于介绍萌娘百科中一些特定功能的操作方法;
  • 本文仅是一篇论述,不属于方针或指引。如果本指南与相关方针或指引发生冲突或存在不一致的情况,请以方针或指引的条文为准。
正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。
——维基百科

本文介绍的大部分正则语法,在不同的正则表达式引擎中都可以使用,但也有一些会有所差异。仅会进行基础性讲解,毕竟深层次的也不会来萌百找

如要在萌百使用,见段落在萌百

可利用萌百的默认编辑器在沙盒中测试,或者在正则表达式测试网站(RegExr/Rubular)中测试(推荐)

基本语法

学习正则表达式的最好方法是理解例子后再自己对例子进行修改、实验。

一个最简单的匹配例子:ab,在大部分正则工具中,都有忽略大小写的选项,所以它会匹配abaBAbAB,而在没有忽略大小写的匹配中,它仅仅只能匹配ab

不过通过正则表达式中的元字符,它就可以匹配更多...

元字符

绿色表示能匹配,无标色或红色划重点表示不能匹配。)

元字符能匹配除其本身之外的更多东西,举个例子:

  • .匹配换行符(\n)外所有字符,如h.b能匹配

hab
hfb
h
b

这就是一个典型的元字符,其中提到了不能匹配的\n,也是一个元字符。

  • \n匹配换行,如uwh\npo能匹配

uwh
po

uwhpo

你也可以尝试以下这些常用的元字符来深入了解正则:

  • \w匹配字母不分大小写、数字或下划线[1],如\wu\wb\w能匹配

au_b4
_uhb5
啊u b不
au bb

也就是说它不能匹配汉字或空格。

  • \d匹配纯数字,如yuo\dliu能匹配

yuo7liu
yuo2liu
yuowliu
yuo你liu

  • \s:匹配空白字符(包括 \n \f\r\t\v,其中一些后面会谈到),如\s\s能匹配

  
 y


单词边界

以下介绍两个特殊的元字符:

  • ^匹配字符串的开始
  • $匹配字符串的结束,根据概念很难理解,那举些例子:

642能匹配

4254642
42546424254
6424254
642
145

^642$只能匹配

4254642
42546424254
6424254
642
145

还有些类似的

  • \b\y匹配单词开始或结束处[2],类似于^$之一。如\b\w5\w会匹配

a5das
4461854274
44424a5a
b5a

  • \m\<单词的开始处[2]
  • \>单词的结束处[2],由于能用到这些的一般都不会来看这里的参考,故不做展示。

以下这些同样是元字符,但它有了更多的用法...

字符转义

如果你想查找元字符本身的话,比如你查找.,就出现了问题:你没办法找到它们,因为它们会被解释未元字符的意义。这时你就得使用

  • \来取消这些字符的特殊意义。如\\匹配

\d

重复

你是不是感觉\w\w\w\w太累赘了,那就使用以下元字符吧:

  • *重复零次或更多次
  • +重复一次或更多次
  • ?重复零次或一次,不懂?边讲边练练。

啊5*啊,匹不匹配5或多个都一样:

啊5啊
啊5555555555555啊
啊啊
啊6啊

同一个例子,换成啊5+啊就不一样了:

啊5啊
啊5555555555555啊
啊啊
啊6啊

还是同一个例子,换成啊5?啊又双叒叕不一样了:

啊5啊
啊5555555555555啊
啊啊
啊6啊

看明白例子了吗?其实很简单的。明白了后还有另一类重复方式:

  • {n}重复n次,可以看做一个数的集合;
  • {n,}重复n次或更多次,类似区间$[n,+∞)$(当然学校没学到就往下看)
  • {n,m}重复n到m次,类似区间$[n,m]$。

啊5{1}啊只能匹配到1个5

啊5啊
啊5555555555555啊
啊啊
啊6啊

啊5{1,}啊能匹配到大于等于1个5

啊5啊
啊5555555555555啊
啊啊
啊6啊

同理,啊5{0,}啊能匹配到大于等于0个5

啊5啊
啊5555555555555啊
啊啊
啊6啊

啊5{0,1}啊能匹配到大于等于0个、小于等于1个5

啊5啊
啊5555555555555啊
啊啊
啊6啊

字符类

如果你想匹配没有预定义元字符的字符集合...

  • [xyz]字符集合,匹配所包含的任意一个字符,如,你想找字母“a”、数字“6”、汉字“猫”和符号“?”,就可以用[a6猫?]

a468448d
so6
噬元兽是
是吗?

嫌写太多?可以这样简写一点:

  • [a-z]字符范围,匹配指定范围内的任意字符,如果想找“f”到“p”的英文字母,就可以用[f-p]

ab14cb4gq
454awahaas
f
**k

当然,你可以同时使用,如[. a-c1-4]

7588746568
eyhtr.ghyuy
iyreh poijlio
人设就诶听b额皮肤

同时,\uXXXX也是能指定范围,如[\u4e00-\uf9a5]+

可以匹配所有中文字符

其实,\w相当于[A-Za-z0-9_]\d相当于[0-9]\s相当于[  \f\n\r\t\v]

用上面的例子,[A-Za-z0-9_]u[A-Za-z0-9_]b[A-Za-z0-9_]也能匹配

au_b4
_uhb5
啊u b不
au bb

(点击展开了解更多方括号表达式)

上面举例的这些其实都是方括号表达式,方括号表达式定义了一个字符序列,所以它的操作可以非常复杂

  • 并集,方括号表达式可以像集合那样操作,所以,你想要匹配两个方括号表达式的并集,可以这样做:[0-3[6-9]],可以匹配[3]

7
2
5

  • [:name:]匹配ASCII字符组name[4],增加命名字符类(named character class)[5]中的字符到表达式
  • [=elt=]增加当前locale下排序(collate)等价于字符“elt”的元素
  • [.elt.]增加排序元素(collation element)elt到表达式中

有时候,我们想找的不属于某个能简单定义的字符类的字符怎么办呢?

反义

  • [^x]匹配除了x以外的字符,如[^a-zA-Z]+能匹配

adsasafsa4245%(#*&42adfsaf
uyhkuyhk按发@)$*放shrgs

  • \D匹配一个非数字字符,等价于[^\d][^0-9],如\D+能匹配

720720adfs)(#%*af428738393
75085720按@$()*发放4542354

  • \W匹配非字母不分大小写、数字或下划线[1][^\w],如\W+\w+\W+能匹配

72__OHSF)fhf(#%*4afaf73ADA93
75afva720按@$(452)*发放4542354

  • \S匹配非空白符的字符,等价于[^\s],而[\S\s]+就能匹配全文

4eaohfoUIDAGUI648
sfukhkuse)$@*(&@4)sofeho   adda
)()*&¥@)()@¥*58

  • \B\Y非单词开始或结束处[2]
  • \W单词的结束处[2]
  • 差集,如果匹配差集,实质上就是减操作,如[a-z&&[^m-r]]可以匹配

c
z
o

如果想查找多次,那我们有什么简便方法吗?

分枝条件

  • x|y有x条件和y条件,满足其中任意一种条件都能匹配,对整个正则表达式生效,如abc|123可以匹配

gkytgkc79876
seydut276877687

有了这个,我们可以将多次查询综合成一次查询,如新曲|知名度|歌姫|涙可以匹配

謎(知名度的な意味で)のP集団によって
奇跡の新曲が生み出されようとしている。
われらの歌姫が名作絵本のコンセプトをもとに歌い上げる、
感動巨編一大スペクタクル
止まらぬ・そして謎の物体
スイミーとは…オランダ出身の絵本作家レオ・レオニ (Leo Lionni) 作の名作絵本。
他の仲間と違い一匹だけ真っ黒な小魚・スイミーが、仲間たちと協力し合って
自分の何十倍もの大きさの魚に立ち向かう、勇気と友情の物語。

有人说,想用<div>.+</div>匹配<div><div></div></div>中内层div,却把全文匹配了,为什么?因为
你呀,真是勤勉呢!アナタ、勤勉ですねぇ!

贪婪之罪和怠惰之罪

  • *?重复任意次,但尽可能少重复
  • +?重复1次或更多次,但尽可能少重复
  • ??重复0次或1次,但尽可能少重复
  • {n,m}?重复n到m次,但尽可能少重复
  • {n,}?重复n次以上,但尽可能少重复

通过一段话和b[ab]*b来区分不同的效果

bbbabaabaabaaabaaaab

没有?,它会一次匹配完b[ab]*b

bbbabaabaabaaabaaaab

有了?,它四次才匹配完b[ab]*?b

bb
babaa
baabaaa
baaaab

这时候有人说了,哎呀你这个|在有相同内容时总是要重复写...

分组语法

  • (abc)将小括号内的内容视为一个整体,并捕获文本到自动命名组“x”里
  • (?:abc)非捕获组,内容不会被捕获,也不会占用捕获组的序号,仅用于条件判断。
  • a(?:x|y)c分枝条件仅对小括号内生效

没学这个之前,写累赘的写成絶対絶対絶対絶対絶対|僕は絶対|そして絶対,现在,写成((?:絶対){4}|僕は|そして)絶対也能达到同样效果

僕は絶対
絶対絶対絶対絶対絶対
何者にもなれず
繰り返してく街の中で窒息する
そして絶対
絶対絶対絶対絶対

Unicode、POSIX

Unicode、POSIX
Unicode处理
Unicode Property:
字符属于标点、空格、字母等等,每个Unicode字符只能属于唯一Unicode Property。
字符\p{L} \p{Ll}\p{Lowercase_Letter} 小写字符(必须有大写的形式)
\p{Lu}\p{Uppercase_Letter} 大写字符(必须有小写的形式)
\p{Lt}\p{Titlecase_Letter} 全词首字母大写的字符
\p{L&}\p{Cased_Letter} 存在大小写形式的字符
\p{Lm}\p{Modifier_Letter} 音标修饰字符
\p{Lo}\p{Other_Letter} 不具有大小写的字符或字形
附加符号\p{M} \p{Mn}\p{Non_Spacing_Mark} 与其他字符结合,不额外占用空间的字符
\p{Mc}\p{Spacing_Combining_Mark} 与其他字符结合,额外占用空间的字符
\p{Me}\p{Enclosing_Mark} 包含其他字符的字符
分隔符\p{Z} \p{Zs}\p{Space_Separator} 不可见的空格,但占据空间
\p{Zl}\p{Line_Separator} 分隔线字符U+2028
\p{Zp}\p{Paragraph_Separator} 分段字符U+2029
符号\p{S} \p{Sm}\p{Math_Symbol} 数学符号
\p{Sc}\p{Currency_Symbol} 通货符号
\p{Sk}\p{Modifier_Symbol} 组合为其他字符的符号
\p{So}\p{Other_Symbol} 其他符号
数值字符\p{N} \p{Nd}\p{Decimal_Digit_Number} 所有文本中的数字0至9字符,不含形意符号
\p{Nl}\p{Letter_Number} 看起来像字母的符号,包含罗马数字
\p{No}\p{Other_Number} 上角标或下角标数字,或者其他不属于0至9的数字,不含形意符号
标点符号\p{P} \p{Pd}\p{Dash_Punctuation} 任何种类的连字号或连接号
\p{Ps}\p{Open_Punctuation} 任何种类开括号
\p{Pe}\p{Close_Punctuation} 任何种类闭括号
\p{Pi}\p{Initial_Punctuation} 任何种类开引号
\p{Pf}\p{Final_Punctuation} 任何种类闭引号
\p{Pc}\p{Connector_Punctuation} 连接词的标点符号
\p{Po}\p{Other_Punctuation} 其他标点符号
其它符号\p{C}(包括不可见控制字符与未用码位) \p{Cc}\p{Control} ASCII或Latin-1控制字符0x00-0x1F0x7F-0x9F
\p{Cf}\p{Format} 不可见的格式化指示字符
\p{Co}\p{Private_Use} 私用码位
\p{Cs}\p{Surrogate} UTF-16编码的代理对的一半
\p{Cn}\p{Unassigned} 未被使用的码位
Unicode Block:按照编码区间划分Unicode字符,每个Unicode Block中的字符编码属于一个编码区间 Unicode Script:按照字符所属的书写系统来划分Unicode字符
POSIX字符组
POSIX字符组 说明 ASCII环境 Unicode环境
[:alnum:] 字母字符和数字字符 [a-zA-Z0-9] [\p{L&}\p{Nd}]
[:alpha:] 字母 [a-zA-Z] \p{L&}
[:ascii:] ASCII字符 [\x00-\x7F] \p{InBasicLatin}
[:blank:] 空格字符和制表符 [ \t] [\p{Zs}\t]
[:cntrl:] 控制字符 [\x00-\x1F\x7F] \p{Cc}
[:digit:] 数字字符 [0-9] \p{Nd}
[:graph:] 空白字符之外的字符 [\x21-\x7E] [^\p{Z}\p{C}]
[:lower:] 小写字母字符 [a-z] \p{Ll}
[:print:] 类似[:graph:],但包括空白字符 [\x20-\x7E] \P{C}
[:punct:] 标点符号 [][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] [\p{P}\p{S}]
[:space:] 空白字符 [ \t\r\n\v\f] [\p{Z}\t\r\n\v\f]
[:upper:] 大写字母字符 [A-Z] \p{Lu}
[:word:] 字母字符 [A-Za-z0-9_] [\p{L}\p{N}\p{Pc}]
[:xdigit:] 十六进制字符 [A-Fa-f0-9] [A-Fa-f0-9]

其它

正则表达式引擎/规范特性对比表
字符
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\ 转义单个元字符
\Q...\E 转义多个元字符 Java 6
\x00 ~ \xFF (ASCII 字符)
\n (换行), \r (回车), \t (制表)
\f (换页), \v (竖直制表)
\a (响铃)
\e (转义)
\b (退格), \B (反斜杠, \)
\cA ~ \cZ (控制字符)
\ca ~ \cz (控制字符)
字符类/字符集 [abc]
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
[abc] 字符类
[^abc] 反义字符类
[a-z] 字符类范围
[\d-z] 中的 - 视为字面量
[a-\d] 中的 - 视为字面量
\ 转义单个字符类元字符
\Q...\E 转义多个字符类元字符 Java 6
\d 代表数字 ASCII ASCII ASCII 需配置 ASCII
\w 代表单词字符 ASCII ASCII ASCII 需配置 ASCII
\s 代表空白字符 ASCII ASCII 需配置 ASCII ASCII ASCII
\D\W\S 代表反义字符类
[\b] 匹配反斜杠 (\)
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
. 匹配换行符 (\n) 外所有字符
锚点
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
^ 字符串/行开始
$ 字符串/行结束
\A 字符串开始
\Z 字符串结束,最后一个 \n
\z 字符串结束 \Z
\` 字符串开始
\' 字符串结束
单词边界
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\b 单词的开始或结束处 ASCII ASCII 需配置 ASCII
\B 不是单词的开始处或结束处 ASCII ASCII 需配置 ASCII
\y 单词的开始或结束处
\Y 不是单词的开始处或结束处
\m 单词的开始处
\M 单词的结束处
\< 单词的开始处
\> 单词的结束处
分支条件
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
| 分支条件 \|
量词
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
? 0 ~ 1 次 \?
* 0 次或更多次
+ 1 次或更多次 \+
{n} n 次 \{n\} \{n\}
{n,m} n ~ m 次 \{n,m\} \{n,m\}
{n,} n 次或更多次 \{n,\} \{n,\}
量词后加 ? 转为懒惰模式
分组与后向引用
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(regex) 编号捕获组 \( \) \( \)
(?:regex) 非捕获组
\1 ~ \9 后向引用
\10 ~ \99 后向引用 N/A N/A
前向引用 \1 ~ \9 N/A N/A
嵌套引用 \1 ~ \9 N/A N/A
后向引用不存在的组报错 N/A N/A
后向引用匹配失败的组失败 N/A N/A
修饰器
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?i) 忽略大小写 仅限 /i flag
(?s) 点 (.) 可匹配\n (?m) flag
(?m) 行开始/结束可匹配 ^ / $ 仅限 /m always on flag
(?x) 忽略空白模式 flag
(?n) 显式匹配
(?-ismxn) 关闭模式修饰器
(?ismxn:group) 模式修饰器仅应用于本组
原子组与占位量词(possessive quantifiers)
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?>regex) 原子组
?+*+++{m,n}+ 占位量词
断言
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?=regex) 正预测先行断言
(?!regex) 负预测先行断言
(?<=text) 正回顾后发断言 整个表达式 整个表达式 有限长度 固定长度 可变固定长度 固定长度
(?<!text) 负回顾后发断言 整个表达式 整个表达式 有限长度 固定长度 可变固定长度 固定长度
从上个匹配继续
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\G 匹配尝试的开始
条件
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?(?=regex)then|else) 使用任意断言
(?(regex)then|else)
(?(1)then|else)
(?(group)then|else)
注释
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?#comment)
忽略空白
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
支持忽略空白语法
字符类作为整体 N/A N/A N/A N/A N/A N/A
# 开启注释 N/A N/A N/A N/A N/A N/A
Unicode 字符
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\X Unicode 字素 需配置
\u0000 ~ \uFFFF (Unicode 字符) u"string"
\x{0} ~ \x{FFFF} (Unicode 字符) 需配置
Unicode 属性、脚本与区块
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\pL ~ \pC (Unicode 属性) 需配置
\p{L} ~ \p{C} (Unicode 属性) 需配置
\p{Lu} ~ \p{Cn} (Unicode 属性) 需配置
\p{L&}\p{Letter&} 等同于 [\p{Lu}\p{Ll}\p{Lt}] Unicode 属性 需配置
\p{IsL} ~ \p{IsC} (Unicode 属性)
\p{IsLu} ~ \p{IsCn} (Unicode 属性)
\p{Letter} ~ \p{Other} (Unicode 属性)
\p{Lowercase_Letter} ~ \p{Not_Assigned} (Unicode 属性)
\p{IsLetter} ~ \p{IsOther} (Unicode 属性)
\p{IsLowercase_Letter} ~ \p{IsNot_Assigned} (Unicode 属性)
\p{Arabic} ~ \p{Yi} (Unicode 脚本) 需配置
\p{IsArabic} ~ \p{IsYi} (Unicode 脚本)
\p{BasicLatin} ~ \p{Specials} (Unicode 区块)
\p{InBasicLatin} ~ \p{InSpecials} (Unicode 区块)
\p{IsBasicLatin} ~ \p{IsSpecials} (Unicode 区块)
上方 {} 中的内容忽略大小写
上方语法中长名称允许空格,边字符,下划线 (如 BasicLatin 可写为 Basic-LatinBasic_LatinBasic Latin) Java 5
\P 上方所有 \p 的反义 需配置
\p{^...} 上方所有 \p{...} 的反义 需配置
命名捕获与后向引用
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
(?<name>regex) .Net 风格的命名捕获分组
(?'name'regex) .Net 风格的命名捕获分组
\k<name> .Net 风格的命名后向引用
\k'name' .Net 风格的命名后向引用
(?P<name>regex) Python 风格的命名捕获分组
(?P=name) Python 风格的命名后向引用
多个捕获组同名 N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
XML 字符类
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
\i\I\c\C XML 名称字符类
[abc-[abc]] 字符类差集 2
POSIX 方括号表达式
特性 JGsoft .NET Java Perl PCRE JS Python Ruby Tcl ARE POSIX BRE POSIX ERE GNU BRE GNU ERE XML XPath
[:alpha:] POSIX 字符类 ASCII
\p{Alpha} POSIX 字符类 ASCII
\p{IsAlpha} POSIX 字符类
[.span-ll.] POSIX 排序序列
[=x=] POSIX 等值字符

注脚点击展开
  1. 1.0 1.1 有些引擎中\w是可以匹配汉字的,这里以不匹配汉字作为参考。
  2. 2.0 2.1 2.2 2.3 2.4 ^$\b外的其它单词边界在不同引擎中适用性范围窄,一般不使用。
  3. 其实在大部分引擎中可以这样写:[0-36-9]
  4. 取自微软-正则表达式 2 (re2.h) 语法
  5. 命名字符类。对于C++11的regex_traits::lookup_classname,缺省返回字符类的名字:"alnum", "apha", "blank", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper", "xdigit", "d", "s", "w",取自维基百科

在萌百

使用正则表达式编辑

在萌百,你可以在编辑器中使用正则语法进行替换,只需要打开默认编辑器中的“高级”,点击最右侧的图标,并在弹出的窗口中勾选“使用正则表达式”。

同时各种编辑器,如Wiki+、IPE等也均支持使用正则表达式进行查找的替换。

在模板/页面中使用正则表达式

萌百有一系列模板可以进行正则表达式的匹配和替换,其中{{regex}}调用了PHP的正则表达式,功能最为强大,而其他模板(如{{Str test}}、{{Str match}}、{{Str replace}}等)则使用的是Lua正则表达式,无法实现部分功能,而且还要占用Lua运行限时。关于这些模板的使用可以直接查看对应模板的文档,在此不再赘述。

外部链接