可以使用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; } }
这个块不会做这个工作吗?
一种更通用(且更简单)的解决方案,允许使用非配对引号。
- 找到引用的字符串
-
将’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
-
\G
- 在前一个匹配或每个字符串的开头处锚定每个匹配。 -
(?:^[^']*'|(?<=.))
- 如果它位于字符串的开头,则匹配第一个引号。 -
(?:'[^']*'|[^'x]+)*+
- 匹配任何未加引号的字符块或任何(非引用)字符,直到'x'。
扫描源字符串,除了单个字符后视。
很抱歉打破你的希望,但你需要一个下推式自动机来做到这一点。 这里有更多信息: 下推自动机
简而言之,正则表达式是有限状态机,只能读取并且没有内存,而下推自动机具有堆栈和操作function。
编辑:拼写…