来自字符串结尾的非贪婪匹配与regsub

时间:2015-07-02 06:51:17

标签: regex tcl non-greedy

我有一个如下文件夹路径:

/h/apps/new/app/k1999

我想使用以下正则表达式删除/app/k1999部分:

set folder "/h/apps/new/app/k1999"
regsub {\/app.+$} $folder "" new_folder

但结果是/h:删除了太多元素。

我注意到我应该使用非贪婪匹配,所以我将代码更改为:

regsub {\/app.+?$} $folder "" new_folder

但结果仍是/h。 上述代码有什么问题?

4 个答案:

答案 0 :(得分:3)

非贪婪只是意味着它会尝试匹配最少量的字符,并在整个正则表达式不匹配时增加该数量。相反的 - 贪婪 - 意味着它会尝试尽可能多地匹配字符,并且如果整个正则表达式不匹配则减少该数量。

正则表达式中的

$表示字符串的结尾。因此,something.+$something.+?$将是等效的,只是在匹配之前会进行更多重试。

在您的情况下,/app.+/apps匹配,这是您的字符串中第一次出现/app。您可以通过更明确地添加/后面的/app来修复它:

regsub {/app/.+$} $folder "" new_folder

答案 1 :(得分:2)

如果您希望将app与整个单词匹配,则可以使用Tcl中的\m \M\m

  

\M
  仅在单词的开头匹配   \M只在单词的末尾匹配

我们只需要/,因为\m是一个非单词字符,我们不需要set folder "/h/apps/new/app/k1999" regsub {/app\M.+$} $folder "" newfolder puts $newfolder

/h/apps/new

请参阅word boundaries

结果:app(我们将整个字[^/]+中的所有内容删除到最后。)

如果您只想删除路径中的部分字符串,可以使用否定的类regsub {/app/[^/]+} $folder "" newfolder 来确保只有IDEONE demo

into

答案 2 :(得分:2)

可以使用正则表达式替换操作从路径名中删除目录后缀,但这并不意味着应该

file join {*}[lmap dir [file split $folder] {if {$dir ne {app}} {set dir} break}]
# -> /h/apps/new

路径名是一个字符串,但更恰当的是它是一个目录名列表:

file split $folder
# -> / h apps new app k1999

你想要的是目录名的子列表,但不包括名为" app"的目录。

lmap dir [file split $folder] {if {$dir ne {app}} {set dir} break}
# -> / h apps new

(目录名称可以根据需要进行测试; {$dir ni {foo app bar}}可以跳过其他名称,或者{![string match app-* $dir]}可以使用" app - &#34开头的任何名称;)

当您获得所需的目录名列表时,再次将其元素重新加入路径名,如上所述。

那么为什么要这样做而不是使用正则表达式替换操作呢?这个问题很好地说明了问题。除非一个人是RE专家或者非常注意阅读文档,否则很可能会根据预感制定正则表达式。在最坏的情况下,它第一次工作。如果没有,那么人们很想修补它直到它。任何充分未被理解的(是的, 一个词)RE似乎在大多数情况下会偶尔出现误报和否定以使事情变得有趣。

拆分它,截断它,加入它。不会出错。如果确实如此,那显然是错误的,迫使你解决它。

文档:breakfileiflmapset

答案 3 :(得分:2)

正则表达式引擎总是尽快开始匹配;贪婪并不影响这一点。这意味着在这种情况下,它总是开始匹配太快;你想要最后一场比赛,而不是第一场比赛。

如果您使用regexp -all -indices -inline,则可以找到最后一场比赛的开始位置。然后,您可以删除实际上不想要的部分(例如,将其替换为空字符串:

set folder "/h/apps/new/app/k1999"
set indices [regexp -all -indices -inline {/app} $folder]
# This gets this value: {2 5} {11 14}

# If we have indices — if we had a match — we can do the rest of our processing
if {[llength $indices] > 0} {
    # Get the '11'; the first sub-element of the last element
    set index [lindex $indices end 0]
    # Replace '/app/k1999' with the empty string
    set newfolder [string replace $folder $index end ""]
} else {
    set newfolder $folder;   # In case there's no match...
}