首页 文章

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

提问于
浏览
12

问题:我有成千上万的文档,其中包含我不想要的特定字符 . 例如 . 字符 a . 这些文档包含各种字符,但我想要替换的是在双引号或单引号内的_1492939 .

我想找到并替换它们,我认为需要使用正则表达式 . 我正在使用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

6 回答

  • 9

    Visual Studio代码

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

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

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

    Live demo

    这会在双引号字符串中查找 a ,使单引号字符串用 ' 替换所有 " . 你不能同时拥有两者 .

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

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

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

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

    那个's great. Then I' d建议使用 awksed 或更具编程性的东西来实现你所追求的目标,或者如果你能够使用Sublime Text,就有可能以更优雅的方式解决这个问题 .

    Sublime Text

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

    搜索:

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

    替换为: WHATEVER\3

    Live demo

    RegEx Breakdown

    (?: # 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

    三步法

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

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

    第1步:

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

    替换为: $0Я

    第2步:

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

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

    第3步:

    搜索:

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


  • 2

    首先考虑以下几点:

    • 单引号中可能有多个 a 个字符 .

    • 每个引号(使用单引号或双引号)由开头引号字符,一些文本和相同的结束引号字符组成 . 一种简单的方法是假设当引号字符按顺序计数时,奇数字符是打开引号,偶数字符是关闭引号 .

    • 在第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点提出了一个问题,因为在它之后具有通配符重复字符的捕获组(例如 (.*)* )将仅捕获最后捕获的"thing" . 但好消息是,有一种方法可以在一定范围内解决这个问题 . 许多正则表达式引擎最多允许99个捕获组(*) . 因此,如果我们可以假设每个引用中不会超过99个 UPDATE (或者即使我们不能 - 请参见步骤3),我们可以执行以下操作...

    (*)不幸的是我的第一个停靠点,记事本没有 - 它只允许最多9.不确定VS代码 . 但regex101(用于下面的在线演示)确实如此 .

    TL; DR - 怎么办?

    • 搜索: "([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^a"]*)a*([^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"

    • (如果已全部替换了's a possibility of > 99 such characters in a single quote until they',则可选择继续重复前两个步骤) .

    • 重复步骤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-3 .

    在线演示

    如果您能够将整个文本复制到“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})+ 匹配以下一次或多次 . 这确保了在匹配之前有任何 " 它们是 balancer 的,因为有一个开始和结束的双引号 .

    • (?:"(?:[^"\\\n]|\\.)*){2} 完全匹配以下两次

    • " 按字面意思匹配

    • (?:[^"\\\n]|\\.)* 匹配以下任意一次

    • [^"\\\n] 匹配除 "\\n 之外的任何内容

    • \\. 匹配 \ 后跟任何字符

    • (?<="[^"\n]*) 正向后视确保先于下列内容匹配的内容

    • " 按字面意思匹配

    • [^"\n]* 匹配除 "\n 之外的任何内容

    • a 按字面意思匹配

    • (?=[^"\n]*") 正向前瞻确保后面的内容与以下内容相符

    • [^"\n]* 匹配除 "\n 之外的任何内容

    • " 按字面意思匹配

    您可以从上面的模式中删除 \n ,如下所示 . 我添加它以防万一's some sort of special cases I'm不考虑(即评论)可以在你的文本中打破这个正则表达式 . \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)甚至记事本可以快速修复 .
    这避免了必须使用虚假脚本环境 .

    这两个引擎(分别是Dot-Net和boost)都使用 \G 结构 .
    这是在最后一个停止的位置开始下一场比赛 .

    同样,这只是一个建议 .

    这个正则表达式不会检查整个 balancer 报价的有效性
    字符串提前(但它可以添加一行) .

    这一切都是关于知道引号的内部和外部的位置 .

    我评论了正则表达式,但如果你需要更多信息让我知道 .
    这只是一个建议(我知道你的编辑使用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

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

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

相关问题