重复正则表达式模式

Repeating regex pattern

我有这样的字符串

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;  
  '  > tmp

... some other commands ...

这样做就可以了:

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

最后的/g是重复,代表"global"。它会在上一次匹配结束时继续匹配,直到不再匹配为止,所以我们必须注意匹配的结束位置。这就是 (?=...) 的用途。这是一个 "followed by pattern" 告诉重复不要将它作为 "where you left off" 的一部分包含在上一场比赛中。这样,它通过重新匹配第二个 "word".

从中断的地方开始

开头的 s/ 是替换,所以命令是这样的:

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

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

这只会匹配一行。如果您的模式跨越多行,您将需要一些更高级的代码。它还假定 XML 是正确的,并且在标记之外没有围绕破折号或等号的单词。为了解决这个问题,需要在循环中进行额外的模式匹配以提取 gl 标签包围的值,以便您可以只对这些部分进行替换,例如:

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

您必须编写自己的环绕循环来读取 STDIN 并将读取的行放入 $in。 (您还需要不对 perl 解释器使用 -p 或 -n 标志,因为您正在手动读取输入并打印输出。)然而,上面的 while 循环获取 gl 标签内的所有内容,然后仅在该内容。它打印最后一场比赛(或字符串的开头)和当前比赛之前($p)之间发生的所有内容,并保存在 $e 之后的所有内容,这些内容在循环外的最后一场比赛之后打印。

这需要一个迷你嵌套解析器,我推荐一个脚本,因为它更易于维护

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) = (, , );  # open (tag), text, close (tag)
    $t =~ s/(\w+)/$o$c/g; 
    $t;
}ge;

say $str;

正则表达式为我们提供了内置的 "parsing,",其中与 $tag_re 不匹配的词保持不变。一旦匹配到$tag_re,就在replacement side内部按照要求进行处理。 /e 修饰符使替换端被评估为代码。

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

my $str = q(...);

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

然后在您的 shell 脚本中使用该脚本作为

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

其中 </code> 是 shell 变量,如问题 "Edit" 中所示。 </p> <hr> <p>单行</p> <pre><code>echo "word <gl>aaa</gl> word <gl>aaa-bbb=ccc</gl>" | perl -wpe' s{(<[^>]+>) (.+?) (</[^>]+>)} {($o,$t,$c)=(,,);$t=~s/(\w+)/$o$c/g; $t}gex; '

您的 shell 脚本中的内容变为 echo | perl -wpe'...' > output_file。或者您可以更改代码以从 @ARGV 读取并删除 -n 开关,然后添加一个 print

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

其中一行中的 ... 表示与上面相同的代码,现在需要 say 因为我们没有 -p$_ 处理后打印出来。

shift 从数组的前面取出一个元素并 returns 它。在没有参数的情况下,它会在子例程之外对 @ARGV 执行此操作,如此处(在子例程内,其默认目标是 @_)。