跳转到内容

Tcl 编程/regexp

来自 Wikibooks,开放世界中的开放书籍

另一种语言(我不想称之为“小”)嵌入在 Tcl 中,即正则表达式。它们可能看起来不像你想象的那样 - 但它们遵循(许多)规则。目的是描述要匹配的字符串模式 - 用于搜索、提取或替换子字符串。

正则表达式用于regexpregsub 命令,以及lsearchswitch 的可选使用。请注意,这种语言与 Tcl 本身有很大不同,因此在大多数情况下最好用大括号将 RE 括起来,以防止 Tcl 解析器误解它们。

在进入详细内容之前,让我们先看一些例子

regexp {[0-9]+[a-z]} $input 

如果 $input 包含一个或多个数字,后面跟着一个小写字母,则返回 1。

set result [regsub -all {[A-Z]} $input ""]

从 $input 中删除所有大写字母,并将结果保存到result 变量中。

lsearch -all -inline -regexp $input {^-}

返回列表 $input 中所有以 "-" 开头的元素。

字符类

[编辑 | 编辑源代码]

许多字符只是代表它们自身。例如

a

确实匹配字符 "a"。任何 Unicode 都可以在 \uXXXX 格式中指定。在方括号中(与 Tcl 的方括号不同),定义了一组替代项(一个“类”)

[abc]

匹配 "a"、"b" 或 "c"。两个字符之间的连字符 (-) 表示它们之间的范围,例如

[0-9]

匹配一个十进制数字。要在替代项集中包含文字 "-", 请将其放在首位或末尾

[0-9-]

匹配一个数字或一个减号。用 ^ 开头的方括号类可以被否定,例如

[^0-9]

匹配任何不是十进制数字的字符。句号 "." 表示任何字符的一个实例。如果需要文字 "."(或一般来说,要转义 regexp 语法中具有特殊含义的任何字符),请在它前面放一个反斜杠 "\" - 并确保正则表达式用大括号括起来,以便 Tcl 解析器不会过早地吃掉反斜杠...).

要重复一个字符(集)不止一次,可以在它后面加上量词,例如

a+     matches one or more "a"s,
a?     matches zero or one "a",
a*     matches zero or more "a"s.

还有一种使用大括号进行数字量化的方式(再次强调,与 Tcl 的大括号不同)

a{2}   matches two "a"s - same as "aa"
a{1,5} matches one to five "a"s
a{1,}  one or more - like a+
a{0,1} zero or one - like a?
a{0,}  zero or more - like a*

+ 和 * 量词是“贪婪的”,也就是说它们会消耗尽可能长的子字符串。对于非贪婪的行为,它会提供尽可能短的匹配,在后面添加一个 "?"。示例

% regexp -inline {<(.+)>} <foo><bar><grill>
<foo><bar><grill> foo><bar><grill

这会一直匹配到最后一个闭合括号

% regexp -inline {<(.+?)>} <foo><bar><grill>
<foo> foo

这会一直匹配到第一个闭合括号。

默认情况下,正则表达式可以在字符串中的任何位置匹配。你可以将其限制在开头 (^) 和/或结尾 ($)

regexp {^a.+z$} $input

如果输入以 "a" 开头并以 "z" 结尾,并且它们之间有一个或多个字符,则成功。

正则表达式的一部分可以通过用圆括号 () 将其括起来进行分组。这有几个目的

  • regexpregsub 可以提取或引用这些子字符串
  • 可以控制运算符优先级

"|"(或)运算符具有较高优先级。所以

foo|bar grill

匹配字符串 "foo" 或 "bar grill",而

(foo|bar) grill

匹配 "foo grill" 或 "bar grill"。

(a|b|c) ;# is another way to write [abc]

为了将子字符串提取到单独的变量中,regexp 允许额外的参数

regexp ?options? re input fullmatch part1 part2...

在这里,变量fullmatch 将接收与正则表达式re 匹配的input 的子字符串,而 part1 等接收带括号的子匹配项。由于fullmatch 通常不需要,因此使用变量名 "->" 在那个位置是一个很常见的习惯用法,例如

regexp {(..)(...)} $input -> first second

input 的前两个字符放在变量first 中,将接下来的三个字符放在变量second 中。如果 $input 为 "ab123",则first 将保存 "ab",而second 将保存 "123"。

regsubregexp 中,你可以使用 \1 引用第一个带括号的子匹配项,使用 \2 引用第二个子匹配项,等等。\0 是与上面regexp 中相同的完整匹配项。示例

% regsub {(..)(...)} ab123 {\2\1=\0}
123ab=ab123

这里 \1 包含 "ab",\2 包含 "123",而 \0 是完整匹配 "ab123"。另一个例子,如何在一个字符串中找到四个相同的连续小写字母(第一个出现,然后是三个)

regexp {([a-z])\1{3}} $input

更多示例

[编辑 | 编辑源代码]

解析尖括号内的内容(注意,结果包含完整匹配项和子匹配项,以成对的顺序排列,因此使用foreach 仅提取子匹配项)

% regexp -all -inline {<([^>]+)>} x<a>y<b>z<c>d
<a> a <b> b <c> c

在数字的整数部分的三位数分组之间插入逗号

% regsub -all {\d(?=(\d{3})+($|\.))} 1234567.89 {\0,}
1,234,567.89

在其他国家/地区,你可能会使用撇号 (') 作为分隔符,或者将数字分组为四位数(在日本使用)。

反过来,将这些格式化的数字转换回常规方式以用于计算,任务只是删除所有逗号。这可以用regsub 完成

% regsub -all , 1,234,567.89 ""
1234567.89

但由于任务仅涉及常量字符串(逗号和空字符串),因此在这里不使用正则表达式更高效,而是使用string map 命令

% string map {, ""} 1,234,567.89
1234567.89
华夏公益教科书