4

我正在调查一个正则表达式之谜。我很累,所以我可能会遗漏一些明显的东西——但我看不出有任何原因。

在下面的示例中,我使用 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"

我们在这里想要的是删除每行中所有'=' 实例之前存在的最后一个空格字符。和以前一样,我们不能使用简单的 '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' 后面的空格没有被删除,它上面的空格也没有被删除('height' 那个)。

基本上,似乎 /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 被忽略了。任何想法为什么?

4

2 回答 2

3

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

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]*不会做你认为它做的事情。

正则表达式不是这里最好的工具。使用可以处理带引号的字符串的解析器,例如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 输出,因此您可以看到字符串是如何拆分的。

于 2013-03-08T15:18:26.563 回答
1

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

ttsiodras 你问了两个问题:

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

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

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

这将是一种方法(请参阅此参考):

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

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


最后,如果你想追求消极的前瞻解决方案

$ 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 的评论

于 2013-03-08T23:04:44.667 回答