【问题标题】:Split a string that has white spaces, unless they are enclosed within "quotes"?拆分具有空格的字符串,除非它们包含在“引号”中?
【发布时间】:2013-01-17 06:43:45
【问题描述】:

为了简单起见:

string streamR = sr.ReadLine();  // sr.Readline results in:
                                 //                         one "two two"

我希望能够将它们保存为两个不同的字符串,删除所有空格,引号之间的空格除外。因此,我需要的是:

string 1 = one
string 2 = two two

到目前为止,我发现以下代码有效,但它删除了引号内的空格。

//streamR.ReadLine only has two strings
  string[] splitter = streamR.Split(' ');
    str1 = splitter[0];
    // Only set str2 if the length is >1
    str2 = splitter.Length > 1 ? splitter[1] : string.Empty;

这个的输出变成了

one
two

我研究了Regular Expression to split on spaces unless in quotes,但我似乎无法让正则表达式工作/理解代码,尤其是如何拆分它们,使它们成为两个不同的字符串。那里的所有代码都给我一个编译错误(我正在使用System.Text.RegularExpressions

【问题讨论】:

  • 为此编写自己的解析器可能会更容易 - 正则表达式不适合这种逻辑。
  • 什么编译错误?错误信息是什么?在哪一行?
  • 错误 1 ​​找不到源类型“System.Text.RegularExpressions.MatchCollection”的查询模式的实现。未找到“演员表”。您是否缺少对“System.Core.dll”的引用或“System.Linq”的 using 指令?
  • string.split 完美地执行了我想要的,除了我的报价问题

标签: c# split


【解决方案1】:
string input = "one \"two two\" three \"four four\" five six";
var parts = Regex.Matches(input, @"[\""].+?[\""]|[^ ]+")
                .Cast<Match>()
                .Select(m => m.Value)
                .ToList();

【讨论】:

  • 命令 arg 拆分失败:test --file="some file.txt" 拆分为 3 个字符串,而不是两个。预期输出为:test--file="some file.txt" 我不是正则表达式大师,所以无法修复它。 :(
  • 找到了这个有效的正则表达式:Regex.Split(ConsoleInput, "(?&lt;=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");stackoverflow.com/a/4780801/953414 并解释了它是如何工作的。
  • @ErtürkÖztürk 听起来你想用非单词字符而不是空格来分割,所以用 \W 替换正则表达式中的空格,这意味着任何非单词字符。
  • 请解释你的答案,仅仅给出一个sn-p的代码并不能教任何东西,这个用户将来可能需要就这个主题提出更多的问题,至少,提供学习参考。
  • 这会正确地切断字符串,但不幸的是,这仍然会在输出中留下 \"。
【解决方案2】:

您甚至可以在没有 Regex 的情况下做到这一点:带有 String.Split 的 LINQ 表达式可以完成这项工作。

你可以先用"分割你的字符串,然后用只分割结果数组中具有偶数索引的元素。

var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

对于字符串:

This is a test for "Splitting a string" that has white spaces, unless they are "enclosed within quotes"

它给出了结果:

This
is
a
test
for
Splitting a string
that
has
white
spaces,
unless
they
are
enclosed within quotes

更新

string myString = "WordOne \"Word Two\"";
var result = myString.Split('"')
                     .Select((element, index) => index % 2 == 0  // If even index
                                           ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                           : new string[] { element })  // Keep the entire item
                     .SelectMany(element => element).ToList();

Console.WriteLine(result[0]);
Console.WriteLine(result[1]);
Console.ReadKey();

更新 2

如何定义字符串的引号部分?

我们假设第一个 " 之前的字符串没有被引用。

然后,放在第一个 " 和第二个 " 之前的字符串被引用。第二个" 和第三个" 之间的字符串没有被引用。第三个和第四个之间的字符串被引用,...

一般规则是:第 (2*n-1) 个(奇数)" 和第 (2*n) 个(偶数)" 之间的每个字符串都被引用。 (1)

String.Split是什么关系?

使用默认 StringSplitOption(定义为 StringSplitOption.None)的 String.Split 创建一个包含 1 个字符串的列表,然后在列表中为找到的每个拆分字符添加一个新字符串。

所以,在第一个 " 之前,字符串位于拆分数组中的索引 0 处,在第一个和第二个 " 之间,字符串位于数组中的索引 1 处,第三个和第四个之间,索引 2 , ...

一般规则是:第 n 个和第 (n+1) 个 " 之间的字符串在数组中的索引 n 处。 (2)

给定(1)(2),我们可以得出结论:被引用的部分在分割数组中的奇数索引处。

【讨论】:

  • 看起来不错,但有没有办法将列表分成单独的字符串? (readline 一次只有两个单词,包括引号中的单词)
  • @Teachme 你可以使用 get result[0]result[1]
  • @Teachme 你能试试我在帖子的更新部分写的代码吗?你有什么结果?
  • 我还没关注你。如果引用整个字符串怎么办?然后只有一个字符串,它在位置零,这是偶数。为什么这不是问题?
  • @EamonNerbonne 没错,引用的字符串仍然位于拆分数组中的奇数索引处。
【解决方案3】:

因为自定义解析器可能更适合于此。

这是我曾经写过的东西,当时我有一个涉及括号和空格的特定(并且非常奇怪)解析要求,但它足够通用,几乎可以与任何分隔符和文本限定符一起使用。

public static IEnumerable<String> ParseText(String line, Char delimiter, Char textQualifier)
{

    if (line == null)
        yield break;

    else
    {
        Char prevChar = '\0';
        Char nextChar = '\0';
        Char currentChar = '\0';

        Boolean inString = false;

        StringBuilder token = new StringBuilder();

        for (int i = 0; i < line.Length; i++)
        {
            currentChar = line[i];

            if (i > 0)
                prevChar = line[i - 1];
            else
                prevChar = '\0';

            if (i + 1 < line.Length)
                nextChar = line[i + 1];
            else
                nextChar = '\0';

            if (currentChar == textQualifier && (prevChar == '\0' || prevChar == delimiter) && !inString)
            {
                inString = true;
                continue;
            }

            if (currentChar == textQualifier && (nextChar == '\0' || nextChar == delimiter) && inString)
            {
                inString = false;
                continue;
            }

            if (currentChar == delimiter && !inString)
            {
                yield return token.ToString();
                token = token.Remove(0, token.Length);
                continue;
            }

            token = token.Append(currentChar);

        }

        yield return token.ToString();

    } 
}

用法是:

var parsedText = ParseText(streamR, ' ', '"');

【讨论】:

  • 这显然是最好的解决方案。但它最后缺少一个 } !并在 O(n) 中运行!
  • @mischka 你是对的。找到超过 4 年未被发现的语法错误,您就赢了
  • 这是最好的解决方案。一个问题:对于空行,它返回一个空字符串。我通过将最终产量替换为 if (string.IsNullOrWhiteSpace(token.ToString())) yield break; else yield return token.ToString(); 来解决此问题
  • @MattDG 也可以在初始空检查中解决。该空检查可以替换为if (string.IsNullOrWhiteSpace(line)。这取决于应用程序的需求
  • 同意。最好在初始空检查中检查空格。我现在正在使用那个版本——谢谢!
【解决方案4】:

您可以使用属于Microsoft.VisualBasic.FileIO 命名空间的TextFieldParser 类。 (您需要在项目中添加对 Microsoft.VisualBasic 的引用。):

string inputString = "This is \"a test\" of the parser.";

using (MemoryStream ms = new MemoryStream(Encoding.ASCII.GetBytes(inputString)))
{
    using (Microsoft.VisualBasic.FileIO.TextFieldParser tfp = new TextFieldParser(ms))
    {
        tfp.Delimiters = new string[] { " " };
        tfp.HasFieldsEnclosedInQuotes = true;
        string[] output = tfp.ReadFields();

        for (int i = 0; i < output.Length; i++)
        {
            Console.WriteLine("{0}:{1}", i, output[i]);
        }
    }
}

生成输出:

0:This
1:is
2:a test
3:of
4:the
5:parser.

【讨论】:

  • 不需要使用MemoryStream,TextFieldParser 有一个需要TextReader 的重载,所以你可以简单地将new StringReader(inputString) 传递给构造函数
  • 字符串构造函数需要一个路径作为字符串,而不是要解析的文本
  • 我的意思是采用TextReader 的重载(StringReader 子类可以从字符串创建),TextFieldParserTextReader 读取字符串。查看 TextFieldParserStringReader 构造函数的 msdn 文档
【解决方案5】:

支持双引号。

字符串:

a "b b" "c ""c"" c"

结果:

a 
"b b"
"c ""c"" c"

代码:

var list=Regex.Matches(value, @"\""(\""\""|[^\""])+\""|[^ ]+", 
    RegexOptions.ExplicitCapture)
            .Cast<Match>()
            .Select(m => m.Value)
            .ToList();

可选删除双引号:

Select(m => m.StartsWith("\"") ? m.Substring(1, m.Length - 2).Replace("\"\"", "\"") : m)

结果

a 
b b
c "c" c

【讨论】:

    【解决方案6】:

    Squazz 的回答有一个小问题.. 它适用于他的字符串,但如果您添加更多项目则不行。例如。

    string myString = "WordOne \"Word Two\" Three"
    

    在这种情况下,删除最后一个引号会得到 4 个结果,而不是 3 个。

    这很容易解决.. 只需计算转义字符的数量,如果不均匀,则去掉最后一个(根据您的要求进行调整..)

        public static List<String> Split(this string myString, char separator, char escapeCharacter)
        {
            int nbEscapeCharactoers = myString.Count(c => c == escapeCharacter);
            if (nbEscapeCharactoers % 2 != 0) // uneven number of escape characters
            {
                int lastIndex = myString.LastIndexOf("" + escapeCharacter, StringComparison.Ordinal);
                myString = myString.Remove(lastIndex, 1); // remove the last escape character
            }
            var result = myString.Split(escapeCharacter)
                                 .Select((element, index) => index % 2 == 0  // If even index
                                                       ? element.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                                       : new string[] { element })  // Keep the entire item
                                 .SelectMany(element => element).ToList();
            return result;
        }
    

    我还把它变成了一个扩展方法,使分隔符和转义符可配置。

    【讨论】:

      【解决方案7】:

      OP想要

      ...删除所有空格,除了在引号之间找到的空格

      Cédric Bignon 的解决方案几乎做到了这一点,但没有考虑到引号可能是奇数。从检查这个开始,然后删除多余的,确保我们只有在元素真的被引号封装时才停止拆分。

      string myString = "WordOne \"Word Two";
      int placement = myString.LastIndexOf("\"", StringComparison.Ordinal);
      if (placement >= 0)
      myString = myString.Remove(placement, 1);
      
      var result = myString.Split('"')
                           .Select((element, index) => index % 2 == 0  // If even index
                                                 ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)  // Split the item
                                                 : new string[] { element })  // Keep the entire item
                           .SelectMany(element => element).ToList();
      
      Console.WriteLine(result[0]);
      Console.WriteLine(result[1]);
      Console.ReadKey();
      

      逻辑归功于 Cédric Bignon,我只是添加了一个保护措施。

      【讨论】:

      • 这不起作用:"Some text" test,只返回 1 个结果而不是 2 个
      猜你喜欢
      • 2012-11-17
      • 2015-05-16
      • 2021-12-12
      • 1970-01-01
      • 2015-11-09
      • 2016-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多