【问题标题】:Regex quirk in tcltcl 中的正则表达式怪癖
【发布时间】:2017-12-28 01:54:23
【问题描述】:

这个问题是关于了解 Vivado 中内置的 TCL 8.5 中特定正则表达式的行为,特别是 or-ing 两个正则表达式部分我得到了意想不到的结果:

我致力于使用正则表达式为命令行缩进一段文本。我的第一个想法是将每个newline 替换为newline 一些spaces(为清楚起见,在此处替换为X)以进行缩进,所以:

puts [regsub -all "\n" "foo\nBar\nBaz" "\nXX"]
foo
XXBar
XXBaz

这不会缩进第一行,以匹配我使用^的第一行:

puts [regsub -all "^" "foo\nBar\nBaz" "\nXX"]

XXfoo
Bar
Baz

现在应该只是将两个正则表达式部分与| 组合起来,但是我得到了我无法解释的输出:

puts [regsub -all "^|\n" "foo\nBar\nBaz" "\nXX"]

XXfoo
XX
XXBar
XX
XXBaz

demo

额外的换行符和识别标记 (X) 来自哪里?为什么看起来我得到了两个替换?这是一个错误,还是我对正则表达式语法有一点不了解?

为了完整起见,这里是我现在使用的正则表达式puts [regsub -all -line "^" "foo\nBar\nBaz" "XX"]

【问题讨论】:

  • 有趣的问题。顺便说一句,您可以使用-line 选项代替(?n) -> set t [regsub -all -line "^" $string "XX"]。 IMO 更具可读性。
  • 另外,-linestop 在这里就足够了,或者(?w) 内联选项,"(?w)^" 模式。 -line(?n) 还修改了 . 的行为以及模式中未使用的否定括号表达式。
  • @glennjackman 不错,8.0 documentation 给我的印象是我无法将替换结果作为返回值,必须将其指定为变量。我同意你的版本更具可读性。
  • 注 1) Tcl 8.0 已经有 20 多年的历史了,在现代 Tcl 中会有很多不同的工作方式,并且 2) 虽然我认识到问题是关于正则表达式的,但更好的解决方案是使用::textutil::adjust::indent foo\nBar\nBaz XX 或至少join [lmap line [split foo\nBar\nBaz \n] {format {XX%s} $line}] \n

标签: regex tcl


【解决方案1】:

基本与扩展正则表达式

我认为解释取决于表达式 ^ 被视为基本正则表达式 (BRE),但是当您添加 | 时,它被视为高级正则表达式 (ARE),这是一个扩展正则表达式 (ERE) 的超集。这是基于以下内容,来自re_syntax man page

一个 ARE 是一个或多个分支,用“|”分隔,匹配任何匹配任何分支的任何内容。

谜题的第二部分是^ 在基本和扩展/高级正则表达式中的处理方式不同。在基本的正则表达式中,^ 只有当它是表达式的第一个字符时才具有特殊含义。同样,来自re_syntax man page

BRE 在几个方面与 ERE 不同...^ 是一个普通字符,除了在 RE 的开头或带括号的子表达式的开头,...

换句话说,对于 BRE,^ 只会匹配字符串的开头,但在 ARE 中它将匹配行的开头。

那么,到底发生了什么?

首先,^ 匹配字符串的开头,因此它用替换 \nXX 替换它。接下来,它看到f,然后是o,然后是o,没有一个匹配。然后它看到它匹配的'\n`,所以它用替换替换它。

此时匹配器已经消耗了字符foo\n。剩下的是Bar\nBaz。匹配器现在查看该字符串,并且模式 ^ 匹配,所以它再次用替换替换它。因此,您最终会得到两份替换字符串,一份用于换行符,一份用于剩余字符串的开头。

在每一行的开头添加一些东西

如果您的最终目标是为每一行添加缩进,您可以使用带有 regsub 的换行敏感匹配,然后使用 ^ 匹配包括第一行在内的每一行,而不是尝试同时匹配换行符和字符串的开头.为此,您可以将--line 选项添加到regsub。例如:

regsub -line -all "^" "foo\nBar\nBaz" "XX" t; puts $t

【讨论】:

  • 感谢非常详细的解释,现在我只想知道是谁提出了输入解析行为根据输入(而不是 明确地基于标志)。
  • 它似乎没有更改为 BRE 模式,而是看起来像是 RE 引擎中的一个普通的旧错误。嗯。 (另外,使用-line "^";更清晰。)
  • @DonalFellows:我建议将其更改为 BRE 模式,而不是 。但我会听从你的分析。我的直觉反应是这是一个错误,但后来我遵循了我自己的建议“当你认为你在 Tcl 中发现了一个错误时,你可能错了”。 :-)
  • 模式切换肯定是一个错误,因为 BRE 模式应该只在请求时启用(通过记录但很少使用的(?b) 标志)但我真的不认为这就是问题所在。问题(可能)是^ 在换行符处匹配,即使不在-line 模式下也是如此。它可能与 RE 引擎中的代码有关,该代码确定匹配重新开始以查找第二个或更晚的匹配时是否处于开始状态……但我首先犹豫要窥视 RE 引擎的内部。 (那个代码真的很吓人!)
  • 我认为这个问题可以归结为regexp -all {^|\n} "foo\nbar" 返回 3 而不是 2;这是真正出乎意料的结果,因为该字符串只有一个开始和一个嵌入的换行符。 Issue filed.
猜你喜欢
  • 2016-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多