可以使用Regex进行这种特殊的字符串操作吗?

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

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

为简单起见,我们假设引号总是成对出现。

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

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

我能用Python做到这一点:

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

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

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

我将Greg Hewgill的python代码转换为C#并且它有效!

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

那个测试通过了。

诀窍是使用非捕获组来匹配我们正在搜索的匹配(字符x )之后的字符串部分。 尝试将字符串匹配到x只会找到第一次或最后一次出现,具体取决于是否使用了非贪婪的量词。 这是格雷格的想法转移到Tcl,带有评论。

设置strIn {axbx'cxdxe'fxgh'ixj'k}
 set regex {(?x)#enable扩展语法 
                                     # - 允许注释,忽略空格
             x#实际匹配
             (?=#不匹配组
                 [^'] *'#匹配当前引用的子字符串的结尾
                                     ##
                                     ##假设引号是成对的,
                                     ##确保我们确实是 
                                    引号子字符串中的##
                                     ##通过确保字符串的其余部分 
                                     ##是我们所期望的
                                     ##
                 (
                     [^'] *#匹配任何非引用的子字符串
                     |  # ...要么...
                     '[^'] *'#任何引用的子字符串,包括引号
                 )*#任意次数
                 $#直到我们用完字符串:)
             )#不匹配组的结束
 }

 #相同的正则表达式没有注释
 set regexCondensed {(?x)x(?= [^'] *'([^'] |'[^'] *')* $)}

设置replRegex {P}
 set nMatches [regsub -all  -  $ regex $ strIn $ replRegex strOut]
把“$ nMatches替换”。
如果{$ nMatches> 0} {
    把“原文:| $ strIn |”
    把“结果:| $ strOut |”
 }
出口

这打印:

 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; } } 

这个块不会做这个工作吗?

一种更通用(且更简单)的解决方案,允许使用非配对引号。

  1. 找到引用的字符串
  2. 将’x’替换为字符串中的’P’

     #!/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 

不是普通的正则表达式。 正则表达式没有“记忆”,因此无法区分“内部”或“外部”引号。

你需要更强大的东西,例如使用gema它会是直截了当的:

 ''=$0 repl:x=P 

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

虽然你可以在Vim中尝试这个,但只有当字符串在一行上时它才能正常工作,并且只有一对。

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

如果还有一对甚至是不平衡的,那么它可能会失败。 这就是我在ex命令中包含c aka confirm标志的方式。

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

一种可能的解决方案是打破成对的线,然后你可以用vim解决方案。

 Pattern: (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x Replacement: \1P 
  1. \G - 在前一个匹配或每个字符串的开头处锚定每个匹配。
  2. (?:^[^']*'|(?<=.)) - 如果它位于字符串的开头,则匹配第一个引号。
  3. (?:'[^']*'|[^'x]+)*+ - 匹配任何未加引号的字符块或任何(非引用)字符,直到'x'。

扫描源字符串,除了单个字符后视。

很抱歉打破你的希望,但你需要一个下推式自动机来做到这一点。 这里有更多信息: 下推自动机

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

编辑:拼写…