我需要在字符串中将字符(例如) x 替换为字符(例如) P ,但前提是它包含在带引号的子字符串中。 一个例子使它更清晰:
axbx'cxdxe'fxgh'ixj'k -> axbx'cPdPe'fxgh'iPj'k
为简单起见,我们假设引号总是成对出现。
显而易见的方法是一次只处理一个字符串(简单的状态机方法);
但是,我想知道是否可以使用正则表达式一次完成所有处理。
我的目标语言是C#,但我想我的问题与任何有正则表达式内置或库支持的语言有关。
答案 0 :(得分:9)
我将Greg Hewgill的python代码转换为C#并且它有效!
[Test]
public void ReplaceTextInQuotes()
{
Assert.AreEqual("axbx'cPdPe'fxgh'iPj'k",
Regex.Replace("axbx'cxdxe'fxgh'ixj'k",
@"x(?=[^']*'([^']|'[^']*')*$)", "P"));
}
那个测试通过了。
答案 1 :(得分:8)
我能用Python做到这一点:
>>> import re
>>> re.sub(r"x(?=[^']*'([^']|'[^']*')*$)", "P", "axbx'cxdxe'fxgh'ixj'k")
"axbx'cPdPe'fxgh'iPj'k"
这样做是使用非捕获匹配(?= ...)来检查字符x是否在带引号的字符串中。它会查找下一个引号之前的一些非引号字符,然后查找单个字符或引用字符组的序列,直到字符串结尾。
这取决于您的假设,即报价始终是平衡的。这也不是很有效。
答案 2 :(得分:2)
诀窍是使用非捕获组来匹配字符串匹配的部分(字符 x ),我们正在搜索。 尝试将字符串匹配到 x 只能找到第一个或最后一个出现,具体取决于是否使用了非贪婪的量词。 这是Greg的想法转移到Tcl,带有评论。
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
打印:
3 replacements.
Original: |axbx'cxdxe'fxgh'ixj'k|
Result: |axbx'cPdPe'fxgh'iPj'k|
答案 3 :(得分:2)
#!/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;
}
}
这个大块不会做这个工作吗?
答案 4 :(得分: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
答案 5 :(得分:1)
答案 6 :(得分:1)
关于平衡文本替换的类似讨论:Can regular expressions be used to match nested patterns?
虽然你可以在Vim中尝试这个,但是只有当字符串在一行上并且只有一对时,它才能正常工作。
:%s:\('[^']*\)x\([^']*'\):\1P\2:gci
如果还有一对甚至是不平衡的,那么它可能会失败。这就是我在c
命令中包含ex
a.k.a.确认标志的方式。
使用sed可以完成相同操作,无需进行交互 - 或使用awk
,因此您可以添加一些互动。
一种可能的解决方案是打破'
对上的行,然后你可以使用vim解决方案。
答案 7 :(得分:1)
Pattern: (?s)\G((?:^[^']*'|(?<=.))(?:'[^']*'|[^'x]+)*+)x
Replacement: \1P
\G
- 在前一个匹配或字符串开头处锚定每个匹配。(?:^[^']*'|(?<=.))
- 如果它位于字符串的开头,则匹配第一个引号。(?:'[^']*'|[^'x]+)*+
- 匹配任何未加引号的字符块或任何(非引用)字符,最多为“x”。通过源字符串进行一次扫描,除了单个字符后视。
答案 8 :(得分:0)
很抱歉打破你的希望,但你需要一个下推式自动机来做到这一点。这里有更多信息: Pushdown Automaton
简而言之,正则表达式,即有限状态机只能读取并且没有内存,而下推自动机具有堆栈和操作功能。
编辑:拼写...