Tcl regexp没有返回所有匹配

时间:2017-09-09 09:43:25

标签: regex tcl

我正在阅读文件,内容如下:

 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

据我所知,返回的匹配结果将存储为包含fullMatchsubMatch的列表。

我使用llength命令进行了双重检查,只有一个fullMatchsubMatch

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个子匹配。

1 个答案:

答案 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)appendclosecontinuefileutil (package)getsifincrlappendopenpackageputsregexpsettextutil (package)whileSyntax of Tcl regular expressions