问题:我有成千上万的文档,其中包含我不想要的特定字符。例如。人物a
。这些文档包含各种字符,但我要替换的a
是双引号或单引号。
我想找到并替换它们,我认为需要使用正则表达式。我正在使用VSCode,但我对任何建议持开放态度。
我的尝试:我能够找到以下正则表达式匹配包含()
内部值的特定字符串。
".*?(r).*?"
但是,这仅突出显示整个报价。我想只突出这个角色。
任何解决方案,可能在正则表达式之外,都是受欢迎的。
示例结果:给定,角色是a
,找到替换为b
Somebody once told me "apples" are good for you
=> Somebody once told me "bpples" are good for you
"Aardvarks" make good kebabs
=> "Abrdvbrks" make good kebabs
The boy said "aaah!" when his mom told him he was eating aardvark
=> The boy said "bbbh!" when his mom told him he was eating aardvark
VS Code使用JavaScript RegEx引擎来实现其查找/替换功能。这意味着与其他版本(如.NET或PCRE)相比,使用正则表达式非常有限。
幸运的是,这种味道支持前瞻和前瞻,你可以寻找但不消耗性格。因此,确保我们在带引号的字符串中的一种方法是在匹配a
之后查找文件/主题字符串底部的引号数为奇数:
a(?=[^"]*"[^"]*(?:"[^"]*"[^"]*)*$)
这将在双引号字符串中查找a
s,以使单引号字符串用"
替换所有'
s。你不能同时拥有两者。
然而,上面的正则表达式存在问题,它与双引号字符串中的转义双引号冲突。如果重要的是要匹配它们,你还有很长的路要走:
a(?=[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*(?:"[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*)*$)
在大文件上应用这些方法可能会导致堆栈溢出,所以让我们看看更好的方法。
我正在使用VSCode,但我对任何建议持开放态度。
那很棒。然后我建议使用awk
或sed
或更具编程性的东西来实现你所追求的目标,或者如果你能够使用Sublime Text,则有机会以更优雅的方式解决这个问题。
这应该适用于具有成千上万行的大型文件,但是要注意它适用于单个字符(这里是a
),经过一些修改可能也适用于单词或子字符串:
搜索:
(?:"|\G(?<!")(?!\A))(?<r>[^a"\\]*+(?>\\.[^a"\\]*)*+)\K(a|"(*SKIP)(*F))(?(?=((?&r)"))\3)
^ ^ ^
替换为:WHATEVER\3
RegEx细分:
(?: # Beginning of non-capturing group #1
" # Match a `"`
| # Or
\G(?<!")(?!\A) # Continue matching from last successful match
# It shouldn't start right after a `"`
) # End of NCG #1
(?<r> # Start of capturing group `r`
[^a"\\]*+ # Match anything except `a`, `"` or a backslash (possessively)
(?>\\.[^a"\\]*)*+ # Match an escaped character or
# repeat last pattern as much as possible
)\K # End of CG `r`, reset all consumed characters
( # Start of CG #2
a # Match literal `a`
| # Or
"(*SKIP)(*F) # Match a `"` and skip over current match
)
(?(?= # Start a conditional cluster, assuming a positive lookahead
((?&r)") # Start of CG #3, recurs CG `r` and match `"`
) # End of condition
\3 # If conditional passed match CG #3
) # End of conditional
最后但并非最不重要的...
匹配引号内的字符是棘手的,因为分隔符完全相同,因此在不查看相邻字符串的情况下,无法区分开关标记。您可以做的是将分隔符更改为其他内容,以便以后查找。
搜索:"[^"\\]*(?:\\.[^"\\]*)*"
替换为:$0Я
搜索:a(?=[^"\\]*(?:\\.[^"\\]*)*"Я)
用你期望的任何东西替换。
搜索:"Я
没有任何东西可以替换掉所有东西。
首先考虑以下几点:
a
字符。It's a shame 'this quoted text' isn't quoted.
在这里,简单的方法会认为有两个引用的字符串:s a shame
和isn
。另一个:This isn't a quote ...'this is' and 'it's unclear where this quote ends'
。我避免试图解决这些复杂问题,并采用下面的简单方法。坏消息是第1点提出了一个问题,因为在它之后具有通配符重复字符的捕获组(例如(.*)*
)将仅捕获最后捕获的“事物”。但好消息是,有一种方法可以在一定范围内解决这个问题。许多正则表达式引擎最多允许99个捕获组(*)。因此,如果我们可以假设每个引号中不会超过99个a
s(更新......或者即使我们不能 - 请参阅步骤3),我们可以执行以下操作...
(*)不幸的是我的第一个调用端口,Notepad ++没有 - 它只允许最多9个。不确定VS Code。但regex101(用于下面的在线演示)确实如此。
"([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*"
"\1\2\3\4\5\6\7\8\9\10\11\12\13\14\15\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\32\33\34\35\36\37\38\39\40\41\42\43\44\45\46\47\48\49\50\51\52\53\54\55\56\57\58\59\60\61\62\63\64\65\66\67\68\69\70\71\72\73\74\75\76\77\78\79\80\81\82\83\84\85\86\87\88\89\90\91\92\93\94\95\96\97\98\99"
"
替换所有'
,即:'([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*'
如果您能够将整个文本复制到“TEST STRING”的内容中,请参阅以下regex101演示,这些演示实际上可用于执行替换:
/(["'])(.*?)(a)(.*?\1)/g
使用替换模式:
$1$2$4
据我所知,VS Code使用与JavaScript相同的正则表达式引擎,这就是我在JS中编写示例的原因。
这样做的问题是,如果你在一组引号中有多个a,那么它将很难提取正确的值,因此需要在它背后的某种代码,或者你,锤击替换按钮直到不再找到匹配,递归模式并删除引号之间的所有a
let regex = /(["'])(.*?)(a)(.*?\1)/g,
subst = `$1$2$4`,
str = `"a"
"helapke"
Not matched - aaaaaaa
"This is the way the world ends"
"Not with fire"
"ABBA"
"abba",
'I can haz cheezburger'
"This is not a match'
`;
// Loop to get rid of multiple a's in quotes
while(str.match(regex)){
str = str.replace(regex, subst);
}
const result = str;
console.log(result);
如果您可以使用Visual Studio(而不是Visual Studio代码),则使用C ++和C#以及uses the .NET Framework regular expressions编写,这意味着您可以使用可变长度的lookbehind来实现此目的。
(?<="[^"\n]*)a(?=[^"\n]*")
在上面的正则表达式中添加一些逻辑,我们可以告诉它忽略前面有偶数量的"
的任何位置。这可以防止引号之外的a
匹配。以字符串"a" a "a"
为例。只匹配此字符串中的第一个和最后一个a
,但中间的那个将被忽略。
(?<!^[^"\n]*(?:(?:"[^"\n]*){2})+)(?<="[^"\n]*)a(?=[^"\n]*")
现在唯一的问题是,如果我们在"
这两个双引号中逃脱了"a\"" a "a"
,这将会破裂。我们需要添加更多逻辑来防止这种行为。幸运的是,this beautiful answer exists正确匹配逃脱的"
。将此逻辑添加到上面的正则表达式中,我们得到以下结果:
(?<!^[^"\n]*(?:(?:"(?:[^"\\\n]|\\.)*){2})+)(?<="[^"\n]*)a(?=[^"\n]*")
我不确定哪种方法最适合你的字符串,但我会详细解释这个最后的正则表达式,因为它也解释了前两个。
(?<!^[^"\n]*(?:(?:"(?:[^"\\\n]|\\.)*){2})+)
负面的背后照明,确保前面的内容与以下内容不符
^
在线的开头断言位置
[^"\n]*
匹配除"
或\n
之外的任何事情
(?:(?:"(?:[^"\\\n]|\\.)*){2})+
匹配以下一次或多次。这确保了在比赛之前有任何"
它们是平衡的,因为有一个开始和结束的双引号。
(?:"(?:[^"\\\n]|\\.)*){2}
完全匹配以下两次
"
按字面意思对比
(?:[^"\\\n]|\\.)*
匹配以下任意一次
[^"\\\n]
匹配除"
,\
和\n
之外的任何东西
\\.
匹配\
,然后是任何角色(?<="[^"\n]*)
正面看后方,确保先于下列内容匹配
"
按字面意思对比
[^"\n]*
匹配除"
或\n
之外的任何事情a
按字面意思对比(?=[^"\n]*")
确定以下内容的正向前瞻符合以下内容
[^"\n]*
匹配除"
或\n
之外的任何事情
"
按字面意思对比您可以从上面的模式中删除\n
,如下所示。我添加它是为了防止出现某些特殊情况我不考虑(即评论)可能会破坏你的文本中的这个正则表达式。 \A
还强制正则表达式从字符串(或文件)的开头而不是行的开头匹配。
(?<!\A[^"]*(?:(?:"(?:[^"\\]|\\.)*){2})+)(?<="[^"]*)a(?=[^"]*")
这就是它在Visual Studio中的样子:
I am using VSCode, but I'm open to any suggestions.
如果你想留在编辑环境中,你可以使用 Visual Studio(> = 2012)甚至是notepad ++用于快速修复。 这避免了必须使用虚假脚本环境。
这两个引擎(分别是Dot-Net和boost)都使用\G
结构。
这是在最后一个停止的位置开始下一场比赛。
同样,这只是一个建议。
这个正则表达式不会检查整个平衡报价的有效性 字符串提前(但它可以添加一行)。
这一切都是关于知道引号的内部和外部的位置。
我评论了正则表达式,但如果您需要更多信息,请告诉我。 这只是一个建议(我知道你的编辑使用ECMAScript)。
找到(?s)(?:^([^"]*(?:"[^"a]*(?=")"[^"]*(?="))*"[^"a]*)|(?!^)\G)a([^"a]*(?:(?=a.*?")|(?:"[^"]*$|"[^"]*(?=")(?:"[^"a]*(?=")"[^"]*(?="))*"[^"a]*)))
替换$1b$2
这里的所有都是它的。
https://regex101.com/r/loLFYH/1
评论
(?s) # Dot-all inine modifier
(?:
^ # BOS
( # (1 start), Find first quote from BOS (written back)
[^"]*
(?: # --- Cluster
" [^"a]* # Inside quotes with no 'a'
(?= " )
" [^"]* # Between quotes, get up to next quote
(?= " )
)* # --- End cluster, 0 to many times
" [^"a]* # Inside quotes, will be an 'a' ahead of here
# to be sucked up by this match
) # (1 end)
| # OR,
(?! ^ ) # Not-BOS
\G # Continue where left off from last match.
# Must be an 'a' at this point
)
a # The 'a' to be replaced
( # (2 start), Up to the next 'a' (to be written back)
[^"a]*
(?: # --------------------
(?= a .*? " ) # If stopped before 'a', must be a quote ahead
| # or,
(?: # --------------------
" [^"]* $ # If stopped at a quote, check for EOS
| # or,
" [^"]* # Between quotes, get up to next quote
(?= " )
(?: # --- Cluster
" [^"a]* # Inside quotes with no 'a'
(?= " )
" [^"]* # Between quotes
(?= " )
)* # --- End cluster, 0 to many times
" [^"a]* # Inside quotes, will be an 'a' ahead of here
# to be sucked up on the next match
) # --------------------
) # --------------------
) # (2 end)
“内部双引号”相当棘手,因为可能会考虑复杂的场景来完全自动化。
“引号括起来”的准确规则是什么?你需要考虑多行报价吗?您是否引用了包含转义引号或引号的字符串,而不是开始/结束字符串引用?
然而,可能有一个相当简单的表达来做你想要的大部分。
搜索表达式:("[^a"]*)a
替换表达:$1b
这不考虑引号的内部或外部 - 您可以直观地做到这一点。但它突出显示从引用到匹配字符的文本,因此您可以快速确定它是否在内部。
如果您可以接受目视检查,那么我们可以构建此模式以包括不同的报价类型以及大小写。