我有一个(可能是非常基本的)问题,关于如何构造(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;
在一个实例上完成技巧。但是我怎么能一口气找到他们呢?
答案 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
(在当前所有答案中都使用)来解决我们要保留在字符串中的匹配项问题,从而使所有匹配项都不是 已消耗。