【问题标题】:A regular expression mystery正则表达式之谜
【发布时间】:2013-03-09 21:31:53
【问题描述】:

我正在调查一个正则表达式之谜。我很累,所以我可能会失踪 一些显而易见的事情 - 但我看不出有任何原因。

在下面的例子中,我使用 perl - 但我第一次看到这个是在 VIM 中, 所以我猜这与多个正则表达式引擎有关。

假设我们有这个文件:

$ cat data
1 =2   3 =4
5 =6  7 =8

然后我们可以删除 '=' 前面的空格...

$ cat data | perl -ne 's,(.)\s+=(.),\1=\2,g; print;'
1=2   3=4
5=6  7=8

请注意,在每一行中,匹配的所有实例都被替换; 我们使用了 /g 搜索修饰符,它不会在第一次替换时停止, 而是继续替换直到行尾。

例如,'=2' 之前的空格和之前的空格 '=4' 被删除; 在同一行

为什么不使用更简单的结构,例如 's, =,=,g'?嗯,我们是 为更困难的情况做准备...右侧 的分配是带引号的字符串,并且可以是 单引号或双引号:

$ cat data2
1 ="2"   3 ='4 ='
5 ='6'  7 ="8"

要做同样的工作(删除等号前的空格), 我们必须小心,因为字符串可能包含相等的 签名 - 所以我们标记我们看到的第一个报价,并寻找它 通过反向引用:

$ cat data2 | perl -ne 's,(.)\s+=(.)([^\2]*)\2,\1=\2\3\2,g; print;'
1="2"   3='4 ='
5='6'  7="8"

我们使用反向引用 \2 来搜索任何不是 与我们第一次看到的引用相同,任意次数 ([^\2]*)。 然后我们搜索原始报价本身 (\2)。如果找到, 我们使用反向引用来引用替换中的匹配部分 目标。

现在看看这个:

$ cat data3 
posAndWidth ="40:5 ="   height        ="1"
posAndWidth ="-1:8 ='"  textAlignment ="Right"

我们在这里想要的是删除存在的 last 空格字符 在所有每行中的“=”实例之前。像以前一样,我们不能使用 一个简单的 's, =",=",g',因为字符串本身可能包含 等号。

所以我们遵循与上面相同的模式,并使用反向引用:

$ cat data3 | perl -ne "s,(\w+)(\s*) =(['\"])([^\3]*)\3,\1\2=\3\4\3,g; print;"
posAndWidth="40:5 ="   height        ="1"
posAndWidth="-1:8 ='"  textAlignment ="Right"

它有效...但仅适用于该行的第一场比赛! 'textAlignment' 后面的空格没有被删除,也没有被删除 在它之上(“高度”)。

基本上,/g 似乎不再起作用:运行相同 不带 /g 的替换命令产生完全相同的输出:

$ cat data3 | perl -ne "s,(\w+)(\s*) =(['\"])([^\3]*)\3,\1\2=\3\4\3,; print;"
posAndWidth="40:5 ="   height        ="1"
posAndWidth="-1:8 ='"  textAlignment ="Right"

似乎在这个正则表达式中,/g 被忽略了。 任何想法为什么?

【问题讨论】:

  • 不是将第一个引号和最后一个引号之间的所有内容都视为带引号的字符串吗?
  • [^\3]* 部分不能在结束引号之外继续匹配,可以吗?
  • 使用您的 perl cmd,我得到了不同的结果 posAndWidth="40:5=" 5= 之间的空格消失了。
  • 从每一行中删除第一个条目时会发生什么?那么第二个匹配吗?如果是这样,你的锚地有问题。如果不是,那就是 RegEx 的问题。乍一看,我也看不出它们有什么缺陷。
  • 获取this table 了解 Perl 反斜杠转义在各种上下文和版本中的含义。简短的故事是字符类中 1-3 位数字之前的反斜杠是八进制数,因此您的 \3\cC\x03\x{0003} — 换句话说,它是一个 Control-C 时在字符类中使用。

标签: regex perl vim


【解决方案1】:

在替换中插入一些调试字符可以解决问题:

use strict;
use warnings;

while (<DATA>) {
    s,(\w+)(\s*) =(['"])([^\3]*)\3,$1$2=$3<$4>$3,g;
    print;                       #  here -^ -^
}

__DATA__
posAndWidth ="40:5 ="   height        ="1"
posAndWidth ="-1:8 ='"  textAlignment ="Right"

输出:

posAndWidth="<40:5 ="   height        ="1>"
posAndWidth="<-1:8 ='"  textAlignment ="Right>"
#            ^--------- match ---------------^

请注意,匹配同时通过两个引号。 [^\3]* 似乎并没有按照你的想法去做。

Regex 不是这里最好的工具。使用可以处理带引号的字符串的解析器,例如Text::ParseWords

use strict;
use warnings;
use Data::Dumper;
use Text::ParseWords;

while (<DATA>) {
    chomp;
    my @a = quotewords('\s+', 1, $_);
    print Dumper \@a;
    print "@a\n";
}

__DATA__
posAndWidth ="40:5 ="   height        ="1"
posAndWidth ="-1:8 ='"  textAlignment ="Right"

输出:

$VAR1 = [
          'posAndWidth',
          '="40:5 ="',
          'height',
          '="1"'
        ];
posAndWidth ="40:5 =" height ="1"
$VAR1 = [
          'posAndWidth',
          '="-1:8 =\'"',
          'textAlignment',
          '="Right"'
        ];
posAndWidth ="-1:8 ='" textAlignment ="Right"

我包含了 Dumper 输出,因此您可以看到字符串是如何拆分的。

【讨论】:

  • 如果 [^\3]* 没有做我想做的事......那么它到底做了什么?它应该匹配除我们开始的引号之外的任何字符 - 所以它应该在第一个结束引号处停止。这是正则表达式引擎中的错误吗?
  • @ttsiodras 在字符类中,我怀疑元字符是否有效。在这种情况下,您试图否定\3,无论它变成什么。你试过use re 'debug'吗?
  • 关于负反向引用的讨论:perlmonks.org/?node_id=747135 它解释了 [^...] 不能像 TLP 预期的那样使用反向引用。
【解决方案2】:

我将详细说明我对 TLP 的回答的评论:

ttsiodras 你问了两个问题:

1- 为什么您的正则表达式没有产生预期的结果?为什么g 标志不起作用?

答案是因为您的正则表达式包含这部分[^\3] 处理不正确:\3 未被识别为反向引用。我找了它,但找不到在字符类中进行反向引用的方法。

2- 如何删除等号前面的空格,而留下引号之后和引号之间的部分?

这是一种方法(见this reference):

$ cat data3 | perl -pe "s,(([\"']).*?\2)| (=),\1\3,g"
posAndWidth="40:5 ="   height       ="1"
posAndWidth="-1:8 ='"  textAlignment="Right"

正则表达式的第一部分捕获引号(单引号或双引号)之间的任何内容并被匹配替换,第二部分对应于等号,前面有您要查找的空格。 请注意,此解决方案只是通过使用非贪婪运算符 *? 来解决关于带有反向引用 [^\3] 的补码字符类运算符的“有趣”部分的工作。


最后如果你想追求negative lookahead solution

$ cat data3 | perl -pe 's,(\w+)(\s*) =(["'"'"'])((?:(?!\3).)*)\3,\1\2=\3\4\3,g'
posAndWidth="40:5 ="   height       ="1"
posAndWidth="-1:8 ='"  textAlignment="Right"

方括号之间的引号部分仍然表示 "[\"']",但我必须在整个 perl 命令周围使用单引号,否则负前瞻 (?!...) 语法会在 bash 中返回错误。

编辑更正了负前瞻的正则表达式:再次注意非贪婪运算符*?g 标志。

编辑考虑到 ttsiodras 的评论:删除了非贪婪运算符。

编辑考虑到 TLP 的评论

【讨论】:

  • 你的答案的第二部分(我开始的负反向引用)不起作用 - 它只删除了第一个等号的空间......
  • perl -ne ' ... print;'perl -pe ' ... '的长版
  • 完美,谢谢。只是为了确保:您添加的 SO 问题的链接使用 ((?!\3).)* - 您使用 (?!\3).* 我不确定您的表格是否正确......从理论上讲,您的表单可以匹配非引号,然后继续匹配任何内容。
  • 已验证 - 不需要非贪婪的星星: perl -pe 's,(\w+)(\s*) =\s*(["'"'"'])((? :(?!\3).)*)\3,\1\2=\3\4\3,g'
  • 你是正确的,backrefs 没有在字符类中扩展。见this table,可下载here。简而言之,它是一个八进制数,因此\3 是字符类中的 Control-C。
猜你喜欢
  • 2013-01-11
  • 2018-03-21
  • 1970-01-01
  • 2013-02-15
  • 1970-01-01
  • 1970-01-01
  • 2016-11-18
  • 2012-02-22
  • 2023-04-07
相关资源
最近更新 更多