如何查找和替换特定字符,但仅限于引号?

问题描述 投票:12回答:6

问题:我有成千上万的文档,其中包含我不想要的特定字符。例如。人物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

regex string visual-studio-code
6个回答
10
投票

Visual Studio Code

VS Code使用JavaScript RegEx引擎来实现其查找/替换功能。这意味着与其他版本(如.NET或PCRE)相比,使用正则表达式非常有限。

幸运的是,这种味道支持前瞻和前瞻,你可以寻找但不消耗性格。因此,确保我们在带引号的字符串中的一种方法是在匹配a之后查找文件/主题字符串底部的引号数为奇数:

a(?=[^"]*"[^"]*(?:"[^"]*"[^"]*)*$)

Live demo

这将在双引号字符串中查找as,以使单引号字符串用"替换所有's。你不能同时拥有两者。

然而,上面的正则表达式存在问题,它与双引号字符串中的转义双引号冲突。如果重要的是要匹配它们,你还有很长的路要走:

a(?=[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*(?:"[^"\\]*(?:\\.[^"\\]*)*"[^"\\]*(?:\\.[^"\\]*)*)*$)

在大文件上应用这些方法可能会导致堆栈溢出,所以让我们看看更好的方法。

我正在使用VSCode,但我对任何建议持开放态度。

那很棒。然后我建议使用awksed或更具编程性的东西来实现你所追求的目标,或者如果你能够使用Sublime Text,则有机会以更优雅的方式解决这个问题。

Sublime Text

这应该适用于具有成千上万行的大型文件,但是要注意它适用于单个字符(这里是a),经过一些修改可能也适用于单词或子字符串:

搜索:

(?:"|\G(?<!")(?!\A))(?<r>[^a"\\]*+(?>\\.[^a"\\]*)*+)\K(a|"(*SKIP)(*F))(?(?=((?&r)"))\3)
                           ^              ^            ^

替换为:WHATEVER\3

Live demo

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

enter image description here

Three-step approach

最后但并非最不重要的...

匹配引号内的字符是棘手的,因为分隔符完全相同,因此在不查看相邻字符串的情况下,无法区分开关标记。您可以做的是将分隔符更改为其他内容,以便以后查找。

Step 1:

搜索:"[^"\\]*(?:\\.[^"\\]*)*"

替换为:$0Я

Step 2:

搜索:a(?=[^"\\]*(?:\\.[^"\\]*)*"Я)

用你期望的任何东西替换。

Step 3:

搜索:

没有任何东西可以替换掉所有东西。



2
投票

首先考虑以下几点:

  1. 单引号中可能有多个a字符。
  2. 每个引号(使用单引号或双引号)由开头引号字符,一些文本和相同的结束引号字符组成。一种简单的方法是假设当引号字符按顺序计数时,奇数字符是打开引号,偶数字符是关闭引号。
  3. 在第2点之后,可能值得进一步考虑是否应该允许单引号字符串。请参阅以下示例:It's a shame 'this quoted text' isn't quoted.在这里,简单的方法会认为有两个引用的字符串:s a shameisn。另一个:This isn't a quote ...'this is' and 'it's unclear where this quote ends'。我避免试图解决这些复杂问题,并采用下面的简单方法。

坏消息是第1点提出了一个问题,因为在它之后具有通配符重复字符的捕获组(例如(.*)*)将仅捕获最后捕获的“事物”。但好消息是,有一种方法可以在一定范围内解决这个问题。许多正则表达式引擎最多允许99个捕获组(*)。因此,如果我们可以假设每个引号中不会超过99个as(更新......或者即使我们不能 - 请参阅步骤3),我们可以执行以下操作...

(*)不幸的是我的第一个调用端口,Notepad ++没有 - 它只允许最多9个。不确定VS Code。但regex101(用于下面的在线演示)确实如此。

TL;DR - What to do?

  1. 搜索:"([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*"
  2. 替换为:"\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"
  3. (如果单个引号中可能有> 99个这样的字符,直到它们全部被替换,则可选择重复前两个步骤。
  4. 重复步骤1,但用正则表达式中的"替换所有',即:'([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*([^a']*)a*'
  5. 重复步骤2-3。

Online demos

如果您能够将整个文本复制到“TEST STRING”的内容中,请参阅以下regex101演示,这些演示实际上可用于执行替换:


1
投票
/(["'])(.*?)(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);

1
投票

如果您可以使用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(?=[^"]*")

You can test this regex here

这就是它在Visual Studio中的样子:

Visual Studio example


0
投票

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)

0
投票

“内部双引号”相当棘手,因为可能会考虑复杂的场景来完全自动化。

“引号括起来”的准确规则是什么?你需要考虑多行报价吗?您是否引用了包含转义引号或引号的字符串,而不是开始/结束字符串引用?

然而,可能有一个相当简单的表达来做你想要的大部分。

搜索表达式:("[^a"]*)a

替换表达:$1b

这不考虑引号的内部或外部 - 您可以直观地做到这一点。但它突出显示从引用到匹配字符的文本,因此您可以快速确定它是否在内部。

如果您可以接受目视检查,那么我们可以构建此模式以包括不同的报价类型以及大小写。

© www.soinside.com 2019 - 2024. All rights reserved.