重复正则表达式模式

时间:2018-07-18 18:53:42

标签: regex perl repeat

我有一个像这样的字符串

word <gl>aaa</gl> word <gl>aaa-bbb=ccc</gl>

其中,如果标签中包含一个或多个单词。在那些单词不止一个的情况下(通常用-或=分隔,可能还包含其他非单词字符),我想确保标签将每个单词分别括起来,以使结果字符串为:

word <gl>aaa</gl> word <gl>aaa</gl>-<gl>bbb</gl>=<gl>ccc</gl>

因此,我试图提出一个正则表达式,该表达式可以找到\ W *?(\ w +)的任意数量的迭代,然后将每个单词分别用标签括起来。理想情况下,我希望将其作为一种代码,可以使用perl从命令行执行,如下所示:

perl -pe 's///g;' in out

这是经过大量的反复试验和谷歌搜索之后我得到的东西-我不是程序员:( ...:

/<gl>\W*?(\w+)\W*?((\w+)\W*?){0,10}<\/gl>/

它找到第一个和最后一个单词(aaa和ccc)。现在,如何使它重复操作并找到其他单词(如果存在)?然后如何获得替代品?任何有关如何执行此操作或在哪里可以找到更多信息的提示将不胜感激?

编辑: 这是工作流程的一部分,该工作流程在shell脚本中进行了其他一些转换:

#!/bin/sh

perl -pe '# 
  s/replace/me/g;  
  s/replace/me/g;  
  ' $1 > tmp

... some other commands ...

2 个答案:

答案 0 :(得分:2)

这需要一个微型嵌套解析器,我建议使用一个脚本,因为它易于维护

use warnings;
use strict;
use feature 'say';

my $str = q(word <gl>aaa</gl> word <gl>aaa-bbb=ccc</gl>);

my $tag_re = qr{(<[^>]+>) (.+?) (</[^>]+>)}x;   # / (stop markup highlighter)

$str =~ s{$tag_re}{
    my ($o, $t, $c) = ($1, $2, $3);  # open (tag), text, close (tag)
    $t =~ s/(\w+)/$o$1$c/g; 
    $t;
}ge;

say $str;

正则表达式为我们提供了内置的“解析”功能,其中不匹配$tag_re的单词保持不变。 $tag_re匹配后,将在替换端内根据需要对其进行处理。 /e修饰符使替换面被评估为代码。

为脚本提供输入的一种方法是通过命令行参数,该参数在脚本的@ARGV全局数组中可用。对于问题“编辑”中指示的用途,请替换硬编码

my $str = q(...);

my $str = shift @ARGV;  # first argument on the command line

,然后在外壳程序脚本中将该脚本用作

#!/bin/sh
...
script.pl $1 > output_file

其中$1是shell变量,如问题的“编辑”所示。


一口气

echo "word <gl>aaa</gl> word <gl>aaa-bbb=ccc</gl>"  |
    perl -wpe'
        s{(<[^>]+>) (.+?) (</[^>]+>)}
         {($o,$t,$c)=($1,$2,$3);$t=~s/(\w+)/$o$1$c/g; $t}gex;
    '

您的shell脚本中的内容将变为echo $1 | perl -wpe'...' > output_file。或者,您可以更改代码以从@ARGV读取并放下-n开关,然后添加打印文件

#!/bin/sh
...
perl -wE'$_=shift; ...; say' $1 > output_file 

其中的...与上面的代码相同,并且现在需要say,因为我们没有-p所用的$_处理后将其打印出来。

shift从数组的前面移走一个元素并将其返回。没有参数,它会在子例程外部(如此处)(在子例程内部,其默认目标为@ARGV)对@_执行此操作。

答案 1 :(得分:-1)

这可以做到:

s/(\w+)([\-=])(?=\w+)/$1<\/gl>$2<gl>/g;

最后的/ g是重复项,代表“全局”。它将在上一场比赛结束时进行比赛,并保持比赛状态,直到不再比赛为止,因此我们必须注意比赛在哪里结束。这就是(?= ...)的目的。这是“后继模式”,告诉重复项在上一场比赛中不将其作为“您离开的地方”的一部分。这样,它通过重新匹配第二个“单词”来拾起遗漏的地方。

开头的s /是一个替换,因此命令将类似于:

cat in | perl -pne 's/(\w+)([\-=])(?=\w+)/$1<\/gl>$2<gl>/g;$_' > out

最后需要$ _,因为全局替换的结果是进行替换的次数。

这将只匹配一行。如果您的模式跨越多行,则需要一些更高级的代码。它还假定XML是正确的,并且在标记之外没有单词包含短划线或等号。为了解决这个问题,必须在循环中进行额外的模式匹配,以拉出gl标记所包围的值,以便您可以仅对那些部分进行替换,例如:

my $e = $in;
while($in =~ /(.*?<gl>)(.*?)(?=<\/gl>)/g){
    my $p = $1;
    my $s = $2;
    print($p);
    $s =~ s/(\w+)([\-=])(?=\w+)/$1<\/gl>$2<gl>/g;
    print($s);
    $e = $';   # ' (stop markup highlighter)
}
print($e);

您必须编写自己的环绕循环才能读取STDIN并将读取的行放入$in中。 (由于您正在读取输入并手动打印输出,因此您也不需要在perl解释器中使用-p或-n标志。)但是,上面的while循环会捕获gl标记内的所有内容,然后仅对gl标记执行替换。该内容。它打印出最后一个匹配(或字符串的开头)到当前匹配($p)之前发生的所有内容,并将$e之后的所有内容保存起来,并在循环外的最后一个匹配之后打印。 / p>