【问题标题】:Regex for splitting a string using space when not surrounded by single or double quotes正则表达式用于在不被单引号或双引号包围时使用空格分割字符串
【发布时间】:2010-09-26 20:13:58
【问题描述】:

我是正则表达式的新手,非常感谢您的帮助。我正在尝试组合一个表达式,该表达式将使用所有未被单引号或双引号包围的空格来拆分示例字符串。我的最后一次尝试看起来像这样:(?!") 并且不太有效。它在引号前的空格上分割。

示例输入:

This is a string that "will be" highlighted when your 'regular expression' matches something.

期望的输出:

This
is
a
string
that
will be
highlighted
when
your
regular expression
matches
something.

注意"will be"'regular expression' 保留单词之间的空格。

【问题讨论】:

  • 您实际上是在使用“split”方法,还是在 Matcher 上使用“find”方法循环就足够了?
  • "现在他有两个问题"

标签: java regex split


【解决方案1】:

我不明白为什么所有其他人都提出如此复杂的正则表达式或如此长的代码。本质上,您想从字符串中获取两种东西:不是空格或引号的字符序列,以及以引号开头和结尾的字符序列,两者之间没有引号,用于两种引号。你可以很容易地用这个正则表达式匹配这些东西:

[^\s"']+|"([^"]*)"|'([^']*)'

我添加了捕获组,因为您不希望列表中出现引号。

此 Java 代码构建列表,如果匹配则添加捕获组以排除引号,如果捕获组不匹配(匹配未引用的单词),则添加整体正则表达式匹配。

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    if (regexMatcher.group(1) != null) {
        // Add double-quoted string without the quotes
        matchList.add(regexMatcher.group(1));
    } else if (regexMatcher.group(2) != null) {
        // Add single-quoted string without the quotes
        matchList.add(regexMatcher.group(2));
    } else {
        // Add unquoted word
        matchList.add(regexMatcher.group());
    }
} 

如果您不介意返回列表中包含引号,则可以使用更简单的代码:

List<String> matchList = new ArrayList<String>();
Pattern regex = Pattern.compile("[^\\s\"']+|\"[^\"]*\"|'[^']*'");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
    matchList.add(regexMatcher.group());
} 

【讨论】:

  • 一月,感谢您的回复。顺便说一句,我是 EditPad 的忠实粉丝。
  • 如果我想在 \" 字符串中允许转义引号怎么办?
  • 这个答案的问题在于引用不匹配:John's mother 结果拆分为[John, s, mother]
  • 要解决 leonbloy 概述的问题,您可以稍微重新排序操作数并省略空格组中的引号:"([^"]*)"|'([^']*)'|[^\s]+
  • 基于此和其他答案,以下正则表达式允许在引号内转义字符:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|[^\s]+。见stackoverflow.com/questions/5695240/…
【解决方案2】:

StackOverflow 上有几个问题使用正则表达式在不同的上下文中涵盖了相同的问题。例如:

更新:用于处理单引号和双引号字符串的示例正则表达式。参考:How can I split on a string except when inside quotes?

m/('.*?'|".*?"|\S+)/g 

使用快速 Perl sn-p 对此进行了测试,输出如下所示。如果它们在引号之间(不确定是否需要),也适用于空字符串或仅限空格的字符串。

This
is
a
string
that
"will be"
highlighted
when
your
'regular expression'
matches
something.

请注意,这确实将引号字符本身包含在匹配的值中,尽管您可以使用字符串替换将其删除,或者修改正则表达式以不包含它们。我暂时把它作为练习留给读者或另一张海报,因为凌晨 2 点已经太晚了,不能再搞乱正则表达式了;)

【讨论】:

  • 我认为您的正则表达式允许不匹配的引号,例如“将是”和“正则表达式”。
  • @Zach - 你说得对,它确实...更新了它以解决这个问题,以防万一
【解决方案3】:

如果你想在字符串中允许转义引号,你可以使用这样的东西:

(?:(['"])(.*?)(?<!\\)(?>\\\\)*\1|([^\s]+))

带引号的字符串将是第 2 组,单个未引用的单词将是第 3 组。

您可以在此处尝试各种字符串:http://www.fileformat.info/tool/regex.htmhttp://gskinner.com/RegExr/

【讨论】:

    【解决方案4】:

    Jan Goyvaerts 的正则表达式是我迄今为止找到的最佳解决方案,但也会创建空(null)匹配,他在他的程序中排除了这些匹配。这些空匹配也出现在正则表达式测试器中(例如 rubular.com)。 如果您将搜索转向(首先查找引用的部分,然后查找空格分隔的单词),那么您可能会这样做一次:

    ("[^"]*"|'[^']*'|[\S]+)+
    

    【讨论】:

      【解决方案5】:
      (?<!\G".{0,99999})\s|(?<=\G".{0,99999}")\s
      

      这将匹配没有被双引号包围的空格。 我必须使用 min,max {0,99999} 因为 Java 不支持 * 和 + 在后视。

      【讨论】:

        【解决方案6】:

        搜索字符串可能会更容易,抓取每个部分,而不是拆分它。

        原因是,您可以在"will be" 之前和之后的空格处拆分它。但是,我想不出任何方法来指定忽略拆分内部之间的空间。

        (不是真正的 Java)

        string = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
        
        regex = "\"(\\\"|(?!\\\").)+\"|[^ ]+"; // search for a quoted or non-spaced group
        final = new Array();
        
        while (string.length > 0) {
            string = string.trim();
            if (Regex(regex).test(string)) {
                final.push(Regex(regex).match(string)[0]);
                string = string.replace(regex, ""); // progress to next "word"
            }
        }
        

        此外,捕获单引号可能会导致问题:

        "Foo's Bar 'n Grill"
        
        //=>
        
        "Foo"
        "s Bar "
        "n"
        "Grill"
        

        【讨论】:

        • 您的解决方案不处理单引号字符串,这是 Carl 示例的一部分。
        【解决方案7】:

        String.split() 在这里没有帮助,因为无法区分引号内的空格(不要拆分)和引号外的空格(拆分)。 Matcher.lookingAt() 可能就是你需要的:

        String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
        str = str + " "; // add trailing space
        int len = str.length();
        Matcher m = Pattern.compile("((\"[^\"]+?\")|('[^']+?')|([^\\s]+?))\\s++").matcher(str);
        
        for (int i = 0; i < len; i++)
        {
            m.region(i, len);
        
            if (m.lookingAt())
            {
                String s = m.group(1);
        
                if ((s.startsWith("\"") && s.endsWith("\"")) ||
                    (s.startsWith("'") && s.endsWith("'")))
                {
                    s = s.substring(1, s.length() - 1);
                }
        
                System.out.println(i + ": \"" + s + "\"");
                i += (m.group(0).length() - 1);
            }
        }
        

        产生以下输出:

        0: "This"
        5: "is"
        8: "a"
        10: "string"
        17: "that"
        22: "will be"
        32: "highlighted"
        44: "when"
        49: "your"
        54: "regular expression"
        75: "matches"
        83: "something."
        

        【讨论】:

          【解决方案8】:

          我喜欢 Marcus 的方法,但是,我对其进行了修改,以便允许引号附近的文本,并支持 " 和 ' 引号字符。例如,我需要 a="some value" 而不将其拆分为 [a =,“一些价值”]。

          (?<!\\G\\S{0,99999}[\"'].{0,99999})\\s|(?<=\\G\\S{0,99999}\".{0,99999}\"\\S{0,99999})\\s|(?<=\\G\\S{0,99999}'.{0,99999}'\\S{0,99999})\\s"
          

          【讨论】:

            【解决方案9】:

            Jan 的方法很棒,但这里还有一个记录在案。

            如果你真的想像标题中提到的那样拆分,保留"will be"'regular expression'中的引号,那么你可以使用直接来自Match (or replace) a pattern except in situations s1, s2, s3 etc的这种方法

            正则表达式:

            '[^']*'|\"[^\"]*\"|( )
            

            左边的两个交替匹配完整的'quoted strings'"double-quoted strings"。我们将忽略这些匹配。右侧匹配并捕获第 1 组的空格,我们知道它们是正确的空格,因为它们没有被左侧的表达式匹配。我们将它们替换为SplitHere,然后拆分为SplitHere。同样,这是一个真正的拆分案例,您需要"will be",而不是will be

            这是一个完整的工作实现(请参阅online demo 上的结果)。

            import java.util.*;
            import java.io.*;
            import java.util.regex.*;
            import java.util.List;
            
            class Program {
            public static void main (String[] args) throws java.lang.Exception  {
            
            String subject = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
            Pattern regex = Pattern.compile("\'[^']*'|\"[^\"]*\"|( )");
            Matcher m = regex.matcher(subject);
            StringBuffer b= new StringBuffer();
            while (m.find()) {
                if(m.group(1) != null) m.appendReplacement(b, "SplitHere");
                else m.appendReplacement(b, m.group(0));
            }
            m.appendTail(b);
            String replaced = b.toString();
            String[] splits = replaced.split("SplitHere");
            for (String split : splits) System.out.println(split);
            } // end main
            } // end Program
            

            【讨论】:

              【解决方案10】:

              如果你使用的是c#,你可以使用

              string input= "This is a string that \"will be\" highlighted when your 'regular expression' matches <something random>";
              
              List<string> list1 = 
                              Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""|'(?<match>[\w\s]*)'|<(?<match>[\w\s]*)>").Cast<Match>().Select(m => m.Groups["match"].Value).ToList();
              
              foreach(var v in list1)
                 Console.WriteLine(v);
              

              我特别添加了“|”来强调您可以指定任何字符来分组短语。 (在这种情况下,我使用 进行分组。

              输出是:

              This
              is
              a
              string
              that
              will be
              highlighted
              when
              your
              regular expression 
              matches
              something random
              

              【讨论】:

                【解决方案11】:

                第一个使用 String.split() 的单行器

                String s = "This is a string that \"will be\" highlighted when your 'regular expression' matches something.";
                String[] split = s.split( "(?<!(\"|').{0,255}) | (?!.*\\1.*)" );
                

                [This, is, a, string, that, "will be", highlighted, when, your, 'regular expression', matches, something.]

                如果空格用单引号或双引号括起来,请不要在空格处拆分
                当左侧的 255 个字符和空白右侧的所有字符既不是单引号也不是双引号时,在空白处拆分

                改编自 original post(仅处理双引号)

                【讨论】:

                  【解决方案12】:

                  我有理由确定仅使用正则表达式是不可能的。检查某些东西是否包含在其他标签中是一种解析操作。这似乎与尝试使用正则表达式解析 XML 的问题相同——它无法正确完成。您可以通过重复应用与引用字符串匹配的非贪婪、非全局正则表达式来获得所需的结果,然后一旦找不到其他任何内容,将其拆分为空格......问题,包括跟踪所有子字符串的原始顺序。最好的办法是编写一个非常简单的函数来遍历字符串并提取出你想要的标记。

                  【讨论】:

                  • 可以使用正则表达式,请参阅我链接到的一些示例。这有一些变化,我在 SO 上看到了几个类似的问题,它们通过正则表达式解决了这个问题。
                  • 知道什么时候不使用正则表达式比能够创建 (?:(['"])(.*?)(?\\ \)*\1|([^\s]+))
                  【解决方案13】:

                  对 Jan 接受的答案进行一些希望有用的调整:

                  (['"])((?:\\\1|.)+?)\1|([^\s"']+)
                  
                  • 允许在带引号的字符串中使用转义引号
                  • 避免重复单引号和双引号的模式;如果需要,这也简化了添加更多引用符号的过程(以增加一个捕获组为代价)

                  【讨论】:

                  • 这会破坏带有撇号的单词,例如you're
                  【解决方案14】:

                  你也可以试试这个:

                      String str = "This is a string that \"will be\" highlighted when your 'regular expression' matches something";
                      String ss[] = str.split("\"|\'");
                      for (int i = 0; i < ss.length; i++) {
                          if ((i % 2) == 0) {//even
                              String[] part1 = ss[i].split(" ");
                              for (String pp1 : part1) {
                                  System.out.println("" + pp1);
                              }
                          } else {//odd
                              System.out.println("" + ss[i]);
                          }
                      }
                  

                  【讨论】:

                  • 你真的应该添加一些解释为什么这应该工作 - 你也可以添加代码以及代码本身中的 cmets - 在当前的形式中,它没有提供任何可以帮助的解释社区的其他人了解您为解决/回答问题所做的工作。这对于已经有答案的问题尤其重要。
                  【解决方案15】:

                  以下返回参数数组。除非包含在单引号或双引号中,否则参数是按空格分隔的变量“命令”。然后修改匹配以删除单引号和双引号。

                  using System.Text.RegularExpressions;
                  
                  var args = Regex.Matches(command, "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'").Cast<Match>
                  ().Select(iMatch => iMatch.Value.Replace("\"", "").Replace("'", "")).ToArray();
                  

                  【讨论】:

                  • 您能否在您的答案中添加一点解释,以便其他人更容易理解?理想情况下,我们希望避免仅使用代码的答案。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-09-02
                  • 1970-01-01
                  • 2018-02-02
                  • 1970-01-01
                  • 2012-11-24
                  相关资源
                  最近更新 更多