正则表达式在字符串中查找(/替换)多个字符实例

时间:2018-10-17 00:27:17

标签: regex perl replace

我有一个(可能是非常基本的)问题,关于如何构造(perl)正则表达式perl -pe 's///g;',该问题将查找/替换指定字符串中给定字符/一组字符的多个实例。最初,我以为g“全局”标志可以做到这一点,但我显然误解了此处非常重要的内容。 :/

例如,我要消除特定字符串(在较大的文本语料库中)中的所有非字母数字字符。仅作为示例,字符串以[开头,后跟@,中间可能有一些字符。

[abc@def"ghi"jkl'123]

以下正则表达式

s/(\[[^\[\]]*?@[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1$2/g;

将找到第一个“,如果我运行了3次,则我将全部3次。 同样,如果我想用其他字符替换非字母数字字符,比如说X。

s/(\[[^\[\]]*?@[^\[\]]*?)[^a-zA-Z0-9]+?([^\[\]]*?)/$1X$2/g; 

在一个实例上完成技巧。但是我怎么能一口气找到他们呢?

3 个答案:

答案 0 :(得分:3)

您的代码无效的原因是/g在替换后不重新扫描字符串。它查找给定正则表达式的所有非重叠匹配项,然后替换其中的替换部分。

[abc@def"ghi"jkl'123]中,只有一个匹配项(这是字符串的[abc@def"部分,带有$1 = '[abc@def'$2 = ''),因此只有前一个{{ 1}}已删除。

在第一个匹配项之后,Perl扫描剩余的字符串(")进行另一个匹配项,但找不到另一个ghi"jkl'123](或[)。


我认为最直接的解决方案是使用嵌套的搜索/替换操作。外部匹配项标识要替换的字符串,内部匹配项进行实际替换。

在代码中:

@

或将每个匹配项替换为s{ \[ [^\[\]\@]* \@ \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9//cdr }xe;

X

我们匹配s{ \[ [^\[\]\@]* \@ \K ([^\[\]]*) (?= \] ) }{ $1 =~ tr/a-zA-Z0-9/X/cr }xe; 的前缀,后跟0个或多个不是[[]的字符,后跟@。 / p>

@用于标记匹配的虚拟开始(即,到目前为止匹配的所有内容都不包括在匹配的字符串中,从而简化了替换操作)。

我们匹配并捕获0个或多个不是\K[的字符。

最后,我们预见了一个后缀](因此它也不是匹配字符串的一部分)。

替换部分是作为一段代码而不是字符串执行的(如]标志所示)。在这里我们可以分别使用/e$1 =~ s/[^a-zA-Z0-9]//gr,但是由于每个内部匹配项只是一个字符,因此也可以使用音译。

我们返回修改后的字符串(如$1 =~ s/[^a-zA-Z0-9]/X/gr标志所示),并将其用作外部/r操作的替换。

答案 1 :(得分:1)

所以...我将建议一种在计算上效率低下的方法。效率极低,但可能仍然比variable-length lookbehind更快,而且(对您而言)也很容易:

\K会导致删除之前的所有内容。...因此仅实际替换其后的字符。

perl -pe 'while (s/\[[^]]*@[^]]*\K[^]a-zA-Z0-9]//){}' file

基本上,我们只有一个空循环,该循环会一直执行到搜索和替换都不会替换任何内容为止。

略有改进的版本:

perl -pe 'while (s/\[[^]]*?@[^]]*?\K[^]a-zA-Z0-9](?=[^]]*?])//){}' file

(?=)验证其内容在比赛之后存在而不是比赛的一部分。这是variable-length lookahead(我们在另一个方向上缺少的东西)。我还使*?变得很懒,所以我们得到了尽可能短的匹配。

答案 2 :(得分:1)

这是另一种方法。精确捕获需要工作的子字符串,并在替换部分中运行正则表达式,以清除其中的非字母数字字符

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

my $var = q(ah [abc@def"ghi"jkl'123] oh); #'
say $var;

$var =~ s{ \[ [^\[\]]*? \@\K ([^\]]+) }{
    (my $v = $1) =~ s{[^0-9a-zA-Z]}{}g;
    $v
}ex;

say $var;

在需要单独的$v以便返回而不是匹配数目的情况下,s/运算符本身将返回。可以通过使用/r修饰符来改善此效果,该修饰符返回更改后的字符串,并且不更改原始字符串(因此,它不会尝试更改$1,这是不允许的)

$var =~ s{ \[ [^\[\]]*? \@\K ([^\]]+) }{
    $1 =~ s/[^0-9a-zA-Z]//gr;
}ex;

\K在那里,因此所有“匹配”之前的匹配都被“丢弃”了-不消耗它们,因此我们无需捕获它们即可将它们放回原处。 /e修饰符使替换零件被评估为代码。

该问题中的代码不起作用,因为所有匹配的内容都被消耗掉了,并且(在/g下)从最后一次匹配之后的位置继续搜索,试图找到整个 再次在弦下进一步排列。这样会失败,只有第一次出现的情况会被替换。

我们通常希望通过\K(在当前所有答案中都使用)来解决我们要保留在字符串中的匹配项问题,从而使所有匹配项都不是 已消耗。