Tcl 编程/regexp
另一种语言(我不想称之为“小”)嵌入在 Tcl 中,即正则表达式。它们可能看起来不像你想象的那样 - 但它们遵循(许多)规则。目的是描述要匹配的字符串模式 - 用于搜索、提取或替换子字符串。
正则表达式用于regexp、regsub 命令,以及lsearch 和switch 的可选使用。请注意,这种语言与 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" 结尾,并且它们之间有一个或多个字符,则成功。
正则表达式的一部分可以通过用圆括号 () 将其括起来进行分组。这有几个目的
- regexp 和regsub 可以提取或引用这些子字符串
- 可以控制运算符优先级
"|"(或)运算符具有较高优先级。所以
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"。
在regsub 和regexp 中,你可以使用 \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