我正在阅读文件,内容如下:
Aug2017:
--------------------------------------
Name Age Phone
--------------------------------------
Jack 25 128736372
Peter 26 987840392
--------------------------------------
Sep2017:
--------------------------------------
Name Age Phone
--------------------------------------
Jared 21 874892032
Eric 24 847938427
--------------------------------------
所以我想在每个虚线之间提取信息并将它们放入列表中。假设$data
包含文件内容,我使用下面的tcl regexp来提取信息:
regexp -all -inline -- {\s+\-{2,}\s+(.*?)\s+\-{2,}\s+} $data
据我所知,返回的匹配结果将存储为包含fullMatch
和subMatch
的列表。
我使用llength
命令进行了双重检查,只有一个fullMatch
和subMatch
。
llength $data
2
为什么只有1 subMatch
?应该有以下5场比赛:
Aug2017:
--------------------------------------
Name Age Phone --> 1st Match
--------------------------------------
Jack 25 128736372
Peter 26 987840392 --> 2nd Match
--------------------------------------
Sep2017: --> 3rd Match
--------------------------------------
Name Age Phone --> 4th Match
--------------------------------------
Jared 21 874892032
Eric 24 847938427 --> 5th Match
--------------------------------------
所以在这种情况下,我选择了subMatch
的第二个列表元素(lindex
)。
lindex [regexp -all -inline -- {\s+\-{2,}\s+(.*?)\s+\-{2,}\s+} $data] 1
然而,我得到的结果是这样的,看起来它是从内容的开头和结尾匹配:
Name Age Phone
--------------------------------------
Jack 25 128736372
Peter 26 987840392
--------------------------------------
Sep2017:
--------------------------------------
Name Age Phone
--------------------------------------
Jared 21 874892032
Eric 24 847938427
我的印象是regexp应该从开头匹配并按顺序匹配到字符串的结尾,不知道为什么tcl正则表达式的行为是这样的?我错过了什么吗?
**我想在这里实现的主要是在虚线分隔符之间提取数据,上面的数据只是一个例子。
预期结果:包含所有匹配项的列表
{ {Name Age Phone} -->1st match
{Jack 25 128736372
Peter 26 987840392} -->2nd match
{Sep2017:} -->3rd match
{Name Age Phone} -->4th match
{Jared 21 874892032
Eric 24 847938427} -->5th match
}
更新: 我稍微改变了我的tcl正则表达式,包括前瞻和@glenn的建议:
regexp -all -inline -expanded -- {\s+?-{2,}\s+?(.*?)(?=\s+?-{2,}\s+?)} $data
我得到的结果(10个子匹配):
{ {----------------------
Name Age Phone} -->1st match
{Name Age Phone} -->2nd match
{----------------------
Jack 25 128736372
Peter 26 987840392} -->3rd match
{Jack 25 128736372
Peter 26 987840392} -->4th match
{----------------------
Sep2017:} -->5th match
{Sep2017:} -->6th match
...
...
}
它非常接近预期的结果,但我仍然想弄清楚如何使用正则表达式来完美匹配预期的5个子匹配。
答案 0 :(得分:2)
正则表达式匹配不是解决此类问题的好工具。使用某种线路滤波器你会好得多。
基于正则表达式的过滤器,与您的示例行紧密匹配:
set f [open data.txt]
while {[gets $f line] >= 0} {
if {[regexp {:} $line]} continue
if {![regexp {\d} $line]} continue
puts $line
}
close $f
基本原理:只有月份名称行有冒号,标题行和分隔符都没有数字。
过滤器不依赖于正则表达式:
set f [open data.txt]
set skip 4
while {[gets $f line] >= 0} {
if {$skip < 1} {
if {[regexp {\-{2,}} $line]} {
set skip 4
} else {
puts $line
}
} else {
incr skip -1
}
}
close $f
此代码读取每一行,在每个月的开头跳过四行,并在一行破折号中断数据时将跳过重置为4.
(注意:表达式\-{2,}
使得短划线在正则表达式中看起来很特殊,因此需要进行转义。实际上,因为短划线是第一个字符表达式,regexp
命令试图将其解释为一个开关。regexp -- {-{2,}} ...
也会起作用,但我觉得它看起来更奇怪。)
ETA (请参阅注释):要在分隔符之间获取数据(即只过滤掉分隔符),请尝试以下操作:
set f [open data.txt]
while {[gets $f line] >= 0} {
if {![regexp {\-{2,}} $line]} {
puts $line
}
}
close $f
或者:
package require fileutil
::fileutil::foreachLine line data.txt {
if {![regexp {\-{2,}} $line]} {
puts $line
}
}
这也应该有效:
regsub -all -line {^\s+-{2,}.*(\n|\Z)} $data {}
启用换行敏感匹配,这将匹配并删除仅包含空格,短划线,可选非换行以及换行符或外部字符串结尾的所有行。
收集匹配列表而不仅仅是打印过滤后的行:
set matches {}
set matchtext {}
::fileutil::foreachLine line data.txt {
if {![regexp {\-{2,}} $line]} {
append matchtext $line\n
} else {
lappend matches $matchtext
set matchtext {}
}
}
运行此变量后,变量matches
包含一个列表,其项目是分隔符之间的连续行。
达到同样目的的另一种方式:
::textutil::splitx $data {(?n)^\s+-{2,}.*(?:\n|\Z)}
(它还在列表的末尾添加了一个空元素,如果有问题,这很容易删除。)
文档: < (operator), >= (operator), append, close, continue, fileutil (package), gets, if, incr, lappend, open, package, puts, regexp, set, textutil (package), while, Syntax of Tcl regular expressions