Regex可以用于此特定的字符串操作吗?

时间:2020-03-06 14:46:32  来源:igfitidea点击:

我需要在字符串中用字符(例如)P替换字符(例如)x,但前提是它包含在带引号的子字符串中。
一个例子使它更清楚:

axbx'cxdxe'fxgh'ixj'k  -> axbx'cPdPe'fxgh'iPj'k

为了简单起见,我们假设报价总是成对出现。

一种明显的方法是一次只处理一个字符的字符串(一种简单的状态机方法)。
但是,我想知道是否可以使用正则表达式一次性完成所有处理。

我的目标语言是C#,但是我想我的问题与任何具有正则表达式的内置或者库支持的语言有关。

解决方案

不能使用普通的正则表达式。正则表达式没有"内存",因此它们无法区分是"内部"还是"外部"引号。

我们需要更强大的功能,例如使用gema会很直接:

'<repl>'=
>>> import re
>>> re.sub(r"x(?=[^']*'([^']|'[^']*')*$)", "P", "axbx'cxdxe'fxgh'ixj'k")
"axbx'cPdPe'fxgh'iPj'k"
repl:x=P

抱歉,希望破灭了,但我们需要使用下推自动机才能做到这一点。这里有更多信息:
下推式自动机

简而言之,作为有限状态机的正则表达式只能读取且没有内存,而下推自动机具有堆栈和操纵功能。

编辑:拼写...

我能够使用Python做到这一点:

:%s:\('[^']*\)x\([^']*'\):P:gci

这是使用非捕获匹配(?= ...)检查字符x是否在带引号的字符串内。它查找直到下一个引号的一些非引号字符,然后查找单个字符或者带引号的字符组的序列,直到字符串的结尾。

这取决于假设,即报价始终是平衡的。这也不是很有效。

关于平衡文本的类似讨论替换为:可以使用正则表达式来匹配嵌套模式吗?

尽管我们可以在Vim中尝试此操作,但是只有当字符串在一行上并且只有一对时,它才能很好地工作。

[Test]
public void ReplaceTextInQuotes()
{
  Assert.AreEqual("axbx'cPdPe'fxgh'iPj'k", 
    Regex.Replace("axbx'cxdxe'fxgh'ixj'k",
      @"x(?=[^']*'([^']|'[^']*')*$)", "P"));
}

如果还有一对甚至是不平衡的',那么它可能会失败。这样我就在ex命令中加入了ca.k.a.确认标志。

使用sed可以完成相同的操作,而无需交互或者使用" awk",因此我们可以添加一些交互。

一种可能的解决方案是打破成对的's,然后就可以使用vim解决方案了。

我将Greg Hewgill的python代码转换为Cand起作用了!

Pattern:     (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x
Replacement: P

那测试通过了。

set strIn {axbx'cxdxe'fxgh'ixj'k}
set regex {(?x)                     # enable expanded syntax 
                                    # - allows comments, ignores whitespace
            x                       # the actual match
            (?=                     # non-matching group
                [^']*'              # match to end of current quoted substring
                                    ##
                                    ## assuming quotes are in pairs,
                                    ## make sure we actually were 
                                    ## inside a quoted substring
                                    ## by making sure the rest of the string 
                                    ## is what we expect it to be
                                    ##
                (
                    [^']*           # match any non-quoted substring
                    |               # ...or...
                    '[^']*'         # any quoted substring, including the quotes
                )*                  # any number of times
                $                   # until we run out of string :)
            )                       # end of non-matching group
}

#the same regular expression without the comments
set regexCondensed {(?x)x(?=[^']*'([^']|'[^']*')*$)}

set replRegex {P}
set nMatches [regsub -all -- $regex $strIn $replRegex strOut]
puts "$nMatches replacements. "
if {$nMatches > 0} {
    puts "Original: |$strIn|"
    puts "Result:   |$strOut|"
}
exit
  • \ G将每个匹配项锚定在上一个匹配项的末尾或者字符串的开头。
  • (?:^ [^'] *'|(?&lt;=。))如果在字符串的开头,则匹配第一个引号。
  • (?:'[^'] *'| [^'x] +)* +匹配任何未加引号的字符块,或者任何(不带引号的)字符,最多匹配'x'。

一遍扫过源字符串,除了后面的单个字符。

诀窍是使用非捕获组来匹配我们要搜索的匹配项(字符x)之后的字符串部分。
尝试将字符串最多匹配x个,只会发现第一次出现还是最后一次出现,这取决于是否使用了非贪婪的量词。
这是格雷格(Greg)的想法,并附有评论。

3 replacements. 
Original: |axbx'cxdxe'fxgh'ixj'k|
Result:   |axbx'cPdPe'fxgh'iPj'k|

打印:

#!/usr/bin/perl -w

use strict;

# Break up the string.
# The spliting uses quotes
# as the delimiter.
# Put every broken substring
# into the @fields array.

my @fields;
while (<>) {
    @fields = split /'/, $_;
}

# For every substring indexed with an odd
# number, search for x and replace it
# with P.

my $count;
my $end = $#fields;
for ($count=0; $count < $end; $count++) {
    if ($count % 2 == 1) {
        $fields[$count] =~ s/a/P/g;
    }    
}

#!/usr/bin/env python
import re

text = "axbx'cxdxe'fxgh'ixj'k"

s = re.sub("'.*?'", lambda m: re.sub("x", "P", m.group(0)), text)

print s == "axbx'cPdPe'fxgh'iPj'k", s
# ->   True axbx'cPdPe'fxgh'iPj'k

这不是做这份工作吗?

一种更通用(更简单)的解决方案,允许不成对的引号。

  • 查找带引号的字符串
  • 将字符串中的" x"替换为" P"
##代码##