一个正则表达神秘

时间:2013-03-08 14:50:57

标签: regex perl vim

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

在下面的示例中,我使用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 空格字符 在 all 每行之前的'='实例之前。像以前一样,我们不能使用 一个简单的'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的replace命令产生完全相同的输出:

$ 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。 有什么想法吗?

2 个答案:

答案 0 :(得分: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输出,因此您可以看到字符串是如何拆分的。

答案 1 :(得分:1)

我将详细阐述我对TLP答案的评论:

你会问两个问题:

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]来围绕具有反向引用*?的补充字符类运算符的“有趣”部分。 p>


最后,如果你想追求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的评论记入帐户