【问题标题】:Regular Expression to split on spaces unless in quotes正则表达式在空格上拆分,除非在引号中
【发布时间】:2010-10-07 22:17:54
【问题描述】:

我想使用 .Net Regex.Split 方法将此输入字符串拆分为一个数组。 除非用引号括起来,否则它必须以空格分隔。

输入: 这是“我的字符串”,它有“六个匹配项”

预期输出:

  1. 这里
  2. 我的字符串
  3. 六场比赛

我需要什么模式?我还需要指定任何 RegexOptions 吗?

【问题讨论】:

  • 是“匹配分隔符”吗?iow,hello"world" 算作 1 个匹配还是 2 个匹配?
  • 好问题.. 我想应该是 2。

标签: c# .net regex


【解决方案1】:

不需要任何选项

正则表达式:

\w+|"[\w\s]*"

C#:

Regex regex = new Regex(@"\w+|""[\w\s]*""");

或者如果您需要排除 " 字符:

    Regex
        .Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));

【讨论】:

  • 非常接近!现在我只需要保留匹配项中的空格。
  • 如果有人感兴趣,这是 Bartek 正则表达式的修改版本,适用于非单词字符(例如句号、逗号和括号):[^\s"]+|"[^"] *"
  • 我知道了:([^\s]*"[^"]+"[^\s]*)|[^"]?\w+[^"]? 现在唯一的问题是它在 Javascript 中不起作用:/ 但这是题外话。
  • 如果字符串可以包含引号,例如“something”、“some other thing”会怎样
  • 修改版,允许使用单引号和双引号分隔符:@"(?&lt;match&gt;\w+)|\""(?&lt;match&gt;[\w\s]*)""|'(?&lt;match&gt;[\w\s]*)'"
【解决方案2】:

Lieven 的解决方案取得了大部分进展,正如他在他的 cmets 中所说,这只是将结局更改为 Bartek 解决方案的问题。最终结果是以下工作正则表达式:

(?<=")\w[\w\s]*(?=")|\w+|"[\w\s]*"

输入:这是“我的字符串”,它有“六个匹配项”

输出:

  1. 这里
  2. “我的字符串”
  3. “六场比赛”

不幸的是,它包括引号。如果您改为使用以下内容:

(("((?<token>.*?)(?<!\\)")|(?<token>[\w]+))(\s)*)

并显式捕获“令牌”匹配,如下所示:

    RegexOptions options = RegexOptions.None;
    Regex regex = new Regex( @"((""((?<token>.*?)(?<!\\)"")|(?<token>[\w]+))(\s)*)", options );
    string input = @"   Here is ""my string"" it has   "" six  matches""   ";
    var result = (from Match m in regex.Matches( input ) 
                  where m.Groups[ "token" ].Success
                  select m.Groups[ "token" ].Value).ToList();

    for ( int i = 0; i < result.Count(); i++ )
    {
        Debug.WriteLine( string.Format( "Token[{0}]: '{1}'", i, result[ i ] ) );
    }

调试输出:

Token[0]: 'Here'
Token[1]: 'is'
Token[2]: 'my string'
Token[3]: 'it'
Token[4]: 'has'
Token[5]: ' six  matches'

【讨论】:

  • 我需要一个用于 javascript split() 函数的正则表达式,用于在空格上拆分单词,引号中的单词除外。你写的我不能用,你知道怎么用javascript写吗?
  • 要更改它以便将其他符号计为单词,只需更改 [\w] 以匹配。它在小数点上拆分,所以我将其更改为 [\w.],现在它可以正确拆分。
  • 那么,当不使用冒号或 : 分割时,正则表达式的外观如何?
【解决方案3】:

最佳答案对我来说不太适用。我试图用空格分割这种字符串,但看起来它也在点('.')上分割。

"the lib.lib" "another lib".lib

我知道这个问题是关于正则表达式的,但我最终编写了一个非正则表达式函数来做到这一点:

    /// <summary>
    /// Splits the string passed in by the delimiters passed in.
    /// Quoted sections are not split, and all tokens have whitespace
    /// trimmed from the start and end.
    public static List<string> split(string stringToSplit, params char[] delimiters)
    {
        List<string> results = new List<string>();

        bool inQuote = false;
        StringBuilder currentToken = new StringBuilder();
        for (int index = 0; index < stringToSplit.Length; ++index)
        {
            char currentCharacter = stringToSplit[index];
            if (currentCharacter == '"')
            {
                // When we see a ", we need to decide whether we are
                // at the start or send of a quoted section...
                inQuote = !inQuote;
            }
            else if (delimiters.Contains(currentCharacter) && inQuote == false)
            {
                // We've come to the end of a token, so we find the token,
                // trim it and add it to the collection of results...
                string result = currentToken.ToString().Trim();
                if (result != "") results.Add(result);

                // We start a new token...
                currentToken = new StringBuilder();
            }
            else
            {
                // We've got a 'normal' character, so we add it to
                // the curent token...
                currentToken.Append(currentCharacter);
            }
        }

        // We've come to the end of the string, so we add the last token...
        string lastResult = currentToken.ToString().Trim();
        if (lastResult != "") results.Add(lastResult);

        return results;
    }

【讨论】:

  • 我希望这个答案不会被视为离题,因为它是一个非正则表达式函数。我在寻找关于如何在保留引号的同时拆分字符串的更一般性主题时发现了这个问题,而不是关于正则表达式的更具体的问题。
  • 这比找出特定的 c# 风格的正则表达式解决方案要清楚得多。
  • 这就是我想要的!工作真棒!
【解决方案4】:

我使用的是 Bartek Szabat 的答案,但我需要在令牌中捕获的不仅仅是“\w”字符。为了解决这个问题,我稍微修改了他的正则表达式,类似于 Grzenio 的回答:

Regular Expression: (?<match>[^\s"]+)|(?<match>"[^"]*")

C# String:          (?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")

Bartek 的代码(返回去掉了引号的标记)变为:

Regex
        .Matches(input, "(?<match>[^\\s\"]+)|(?<match>\"[^\"]*\")")
        .Cast<Match>()
        .Select(m => m.Groups["match"].Value)
        .ToList()
        .ForEach(s => Console.WriteLine(s));

【讨论】:

  • 张贴在这里,希望有人会在遥远的将来发现它有用:)
  • 如果你不想要引号:Regex.Matches(line, "(?&lt;match&gt;[^\\s\"]+)|\"(?&lt;match&gt;[^\"]*)\"")
【解决方案5】:

我发现answer 中的正则表达式非常有用。要使其在 C# 中工作,您必须使用 MatchCollection 类。

//need to escape \s
string pattern = "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'";

MatchCollection parsedStrings = Regex.Matches(line, pattern);

for (int i = 0; i < parsedStrings.Count; i++)
{
    //print parsed strings
    Console.Write(parsedStrings[i].Value + " ");
}
Console.WriteLine();

【讨论】:

    【解决方案6】:

    此正则表达式将根据您在上面给出的情况进行拆分,尽管它不会去除引号或多余的空格,因此您可能需要对字符串进行一些后处理。不过,这应该正确地将带引号的字符串保持在一起。

    "[^"]+"|\s?\w+?\s
    

    【讨论】:

    • 感谢您的回答。这非常接近。足够接近,我现在将使用它。我会把这个问题留一天左右,看看是否有更完整的答案。否则我会接受。
    • "([^"]+)"|\s?(\w+?)\s 将返回 "-stripped 字符串
    【解决方案7】:

    虽然有点混乱,正则语言可以跟踪引号的偶数/奇数计数,但如果您的数据可以包含转义引号 (\"),那么您在生成或理解正则表达式时就会遇到麻烦正确处理。

    【讨论】:

      【解决方案8】:

      肖恩,

      我相信下面的正则表达式应该这样做

      (?<=")\w[\w\s]*(?=")|\w+  
      

      问候,
      利文

      【讨论】:

      • 感谢您的回答,但这似乎没有考虑到引号。
      • 用 Bartek 的正则表达式替换 \w+ 就可以了
      • 那个正则表达式并没有删除 "。
      • 我需要一个用于 javascript split() 函数的正则表达式,用于在空格上拆分单词,引号中的单词除外。你写的我不能用,你知道怎么用javascript写吗?
      【解决方案9】:

      编辑:对不起我之前的帖子,这显然是可能的。

      要处理所有非字母数字字符,您需要这样的东西:

      MatchCollection matchCollection = Regex.Matches(input, @"(?<match>[^""\s]+)|\""(?<match>[^""]*)""");
      foreach (Match match in matchCollection)
              {
                  yield return match.Groups["match"].Value;
              }
      

      如果您使用 .Net >2.0,您可以让 foreach 更智能

      【讨论】:

        【解决方案10】:

        在 Code 项目中查看 LSteinle 的“Split Function that Supports Text Qualifiers

        这是您感兴趣的他的项目中的 sn-p。

        using System.Text.RegularExpressions;
        
        public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)
        {
            string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))", 
                                Regex.Escape(delimiter), Regex.Escape(qualifier));
        
            RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
            if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;
        
            Regex _Expression = New Regex(_Statement, _Options);
            return _Expression.Split(expression);
        }
        

        请注意在循环中调用它,因为每次调用它时都会创建和编译 Regex 语句。因此,如果您需要多次调用它,我会考虑创建某种正则表达式缓存。

        【讨论】:

          【解决方案11】:

          如果您想以免费的开源 javascript 对象的形式查看此问题的通用解决方案,您可以访问http://splitterjsobj.sourceforge.net/ 进行现场演示(并下载)。该对象具有以下特点:

          • 用户定义的引号字符对可用于转义分隔符(防止引号内的拆分)。引号可以使用用户定义的转义字符和/或“双引号转义”进行转义。转义字符可以被转义(用它自己)。在 5 个输出数组之一(对象的属性)中,输出未转义。 (例如,如果转义字符 = /,则 "a///"b" 未转义为 a/"b)
          • 在分隔符数组上拆分;一次调用解析文件。 (输出数组将被嵌套。)
          • javascript 识别的所有转义序列都可以在拆分过程和/或预处理过程中进行评估。
          • 回调功能
          • 跨浏览器一致性

          该对象也可用作 jQuery 插件,但作为此站点的新用户,我只能在此消息中包含一个链接。

          【讨论】:

          • 等等,什么? OP 正在询问 .NET 正则表达式。这是您的 lib 的广告,还是您认为它可以轻松集成到 .NET 中?
          【解决方案12】:

          我需要支持嵌套,所以这些都不适合我。我放弃了尝试通过正则表达式来做,只是编码:

            public static Argument[] ParseCmdLine(string args) {
              List<string> ls = new List<string>();
              StringBuilder sb = new StringBuilder(128);
          
              // support quoted text nesting up to 8 levels deep
              Span<char> quoteChar = stackalloc char[8];
              int quoteLevel = 0;
                
              for (int i = 0; i < args.Length; ++i) {
                char ch = args[i];
                switch (ch) {
                  case ' ':
                    if (quoteLevel == 0) {
                      ls.Add(sb.ToString());
                      sb.Clear();
                      break;
                    } 
                    goto default; 
                  case '"':
                  case '\'':
                    if (quoteChar[quoteLevel] == ch) {
                      --quoteLevel;
                    } else {
                      quoteChar[++quoteLevel] = ch;
                    }
                    goto default; 
                  default:
                    sb.Append(ch);
                    break;
                }
              }
              if (sb.Length > 0) { ls.Add(sb.ToString()); sb.Clear(); }
          
              return Arguments.ParseCmdLine(ls.ToArray());
            }
          

          这里有一些额外的代码来解析对象的命令行参数:

            public struct Argument {
              public string Prefix;
              public string Name;
              public string Eq;
              public string QuoteType;
              public string Value;
          
              public string[] ToArray() => this.Eq == " " ? new string[] { $"{Prefix}{Name}", $"{QuoteType}{Value}{QuoteType}" } : new string[] { this.ToString() };
              public override string ToString() => $"{Prefix}{Name}{Eq}{QuoteType}{Value}{QuoteType}";
            }
          
            private static readonly Regex RGX_MatchArg = new Regex(@"^(?<prefix>-{1,2}|\/)(?<name>[a-zA-Z][a-zA-Z_-]*)(?<assignment>(?<eq>[:= ]|$)(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$))?");
            private static readonly Regex RGX_MatchQuoted = new Regex(@"(?<quote>[""'])?(?<value>.+?)(?:\k<quote>|\s*$)");
          
            public static Argument[] ParseCmdLine(string[] rawArgs) {
              int count = 0;
              Argument[] pairs = new Argument[rawArgs.Length];
          
              int i = 0;
              while(i < rawArgs.Length) {
                string current = rawArgs[i];
                i+=1;
                Match matches = RGX_MatchArg.Match(current);
                Argument arg = new Argument();
                arg.Prefix = matches.Groups["prefix"].Value;
                arg.Name = matches.Groups["name"].Value;
                arg.Value = matches.Groups["value"].Value;
                if(!string.IsNullOrEmpty(arg.Value)) {
                  arg.Eq = matches.Groups["eq"].Value;
                  arg.QuoteType = matches.Groups["quote"].Value;
                } else if ((i < rawArgs.Length) && !rawArgs[i].StartsWith('-') && !rawArgs[i].StartsWith('/')) {
                  arg.Eq = " ";
                  Match quoted = RGX_MatchQuoted.Match(rawArgs[i]);
                  arg.QuoteType = quoted.Groups["quote"].Value;
                  arg.Value = quoted.Groups["value"].Value;
                  i+=1;
                }
                if(string.IsNullOrEmpty(arg.QuoteType) && arg.Value.IndexOfAny(new char[] { ' ', '/', '\\', '-', '=', ':' }) >= 0) {
                  arg.QuoteType = "\"";
                }
                pairs[count++] = arg;
              }
          
              return pairs.Slice(0..count);
            }
          
            public static ILookup<string, Argument> ToLookup(this Argument[] args) => args.ToLookup((arg) => arg.Name, StringComparer.OrdinalIgnoreCase);
          }
          

          它能够解析所有不同类型的参数变体:

          -test -environment staging /DEqTest=avalue /Dcolontest:anothervalue /DwithSpaces="heys: guys" /slashargflag -action="Do: 'The Thing'" -action2 "do: 'Do: \"The Thing\"'" -init
          

          嵌套引号只需要在不同的引号类型之间交替。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-03-31
            • 1970-01-01
            • 2020-11-06
            • 2011-05-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多