在CSV文件中处理逗号

Bob*_*tor 455 csv

我正在寻找有关如何处理正在创建,然后由我们的客户上传的csv文件的建议,并且可能在值中使用逗号,例如公司名称.

我们正在关注的一些想法是:引用标识符(值","值","等)或使用| 而不是逗号.最大的问题是我们必须让它变得简单,否则客户就不会这样做.

Cor*_*ger 389

对于2017年,csv已完全指定 - RFC 4180.

这是一个非常常见的规范,并且被许多库完全覆盖(示例).

只需使用任何易于使用的csv库 - 即RFC 4180.


实际上有CSV格式的规范以及如何处理逗号:

包含换行符(CRLF),双引号和逗号的字段应括在双引号中.

http://tools.ietf.org/html/rfc4180

所以,有价值观foobar,baz,你这样做:

foo,"bar,baz"
Run Code Online (Sandbox Code Playgroud)

另一个需要考虑的重要要求(也来自规范):

如果使用双引号括起字段,则必须通过在其前面添加另一个双引号来转义出现在字段内的双引号.例如:

"aaa","b""bb","ccc"
Run Code Online (Sandbox Code Playgroud)

  • "包含换行符(CRLF),双引号和逗号的字段应括在双引号中." (119认同)
  • "如果使用双引号括起字段,那么出现在字段内的双引号必须通过在其前面加上另一个双引号来转义." (42认同)
  • 不是一个规范,但仍然可能很方便.它说..."目前还没有正式的规范,允许对CSV文件进行各种解释.本节记录了大多数实现似乎遵循的格式." (11认同)
  • 此外,不要忘记,尽管名称如此,但行中的CSV值可能不仅仅用逗号分隔 - 至少在Windows平台上是这样.它取决于当前的区域设置(命令行中的intl.cpl,"高级设置"),特别是列表分隔符:`System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator`. (5认同)
  • 除了链接之外,请在此答案中提供相关信息.A)删除上述大部分评论(和我的评论),B)节省比回答者更多的人去另一页并查找相关内容数据,C)防止Link Rot. (4认同)

har*_*rpo 218

正如其他人所说,你需要转义包含引号的值.这是C♯中的一个小型CSV阅读器,支持引用值,包括嵌入式引号和回车.

顺便说一句,这是经过单元测试的代码.我现在正在发布它,因为这个问题似乎出现了很多,其他人可能不需要整个库,只需简单的CSV支持即可.

您可以按如下方式使用它:

using System;
public class test
{
    public static void Main()
    {
        using ( CsvReader reader = new CsvReader( "data.csv" ) )
        {
            foreach( string[] values in reader.RowEnumerator )
            {
                Console.WriteLine( "Row {0} has {1} values.", reader.RowIndex, values.Length );
            }
        }
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是课程.请注意,您也可以使用该Csv.Escape函数编写有效的CSV.

using System.IO;
using System.Text.RegularExpressions;

public sealed class CsvReader : System.IDisposable
{
    public CsvReader( string fileName ) : this( new FileStream( fileName, FileMode.Open, FileAccess.Read ) )
    {
    }

    public CsvReader( Stream stream )
    {
        __reader = new StreamReader( stream );
    }

    public System.Collections.IEnumerable RowEnumerator
    {
        get {
            if ( null == __reader )
                throw new System.ApplicationException( "I can't start reading without CSV input." );

            __rowno = 0;
            string sLine;
            string sNextLine;

            while ( null != ( sLine = __reader.ReadLine() ) )
            {
                while ( rexRunOnLine.IsMatch( sLine ) && null != ( sNextLine = __reader.ReadLine() ) )
                    sLine += "\n" + sNextLine;

                __rowno++;
                string[] values = rexCsvSplitter.Split( sLine );

                for ( int i = 0; i < values.Length; i++ )
                    values[i] = Csv.Unescape( values[i] );

                yield return values;
            }

            __reader.Close();
        }
    }

    public long RowIndex { get { return __rowno; } }

    public void Dispose()
    {
        if ( null != __reader ) __reader.Dispose();
    }

    //============================================


    private long __rowno = 0;
    private TextReader __reader;
    private static Regex rexCsvSplitter = new Regex( @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))" );
    private static Regex rexRunOnLine = new Regex( @"^[^""]*(?:""[^""]*""[^""]*)*""[^""]*$" );
}

public static class Csv
{
    public static string Escape( string s )
    {
        if ( s.Contains( QUOTE ) )
            s = s.Replace( QUOTE, ESCAPED_QUOTE );

        if ( s.IndexOfAny( CHARACTERS_THAT_MUST_BE_QUOTED ) > -1 )
            s = QUOTE + s + QUOTE;

        return s;
    }

    public static string Unescape( string s )
    {
        if ( s.StartsWith( QUOTE ) && s.EndsWith( QUOTE ) )
        {
            s = s.Substring( 1, s.Length - 2 );

            if ( s.Contains( ESCAPED_QUOTE ) )
                s = s.Replace( ESCAPED_QUOTE, QUOTE );
        }

        return s;
    }


    private const string QUOTE = "\"";
    private const string ESCAPED_QUOTE = "\"\"";
    private static char[] CHARACTERS_THAT_MUST_BE_QUOTED = { ',', '"', '\n' };
}
Run Code Online (Sandbox Code Playgroud)

  • @ BenC.R.Leggiero,那么我想你也必须低估这个问题,因为你的标准是无法回答的.实际上,代码相当于简单规范的正式实现,并且可以很容易地转换为任何常用语言. (7认同)
  • @NadaNaeem,谨慎阐述? (3认同)
  • 您可能还需要翻译\ r \n以了解Windows合规性,具体取决于您的应用程序. (2认同)

Rob*_*ble 75

CSV格式使用逗号分隔值,包含回车符,换行符,逗号或双引号的值由双引号括起.引用包含双引号的值,并使用前一个引号对每个文字引号进行转义:例如,3个值:

test
list, of, items
"go" he said
Run Code Online (Sandbox Code Playgroud)

将被编码为:

test
"list, of, items"
"""go"" he said"
Run Code Online (Sandbox Code Playgroud)

任何字段可以报价,但只包含逗号,CR/NL,或报价域必须加引号.

CSV格式没有真正的标准,但几乎所有应用程序都遵循此处记录的约定.其他地方提到的RFC不是CSV的标准,它是在MIME中使用CSV的RFC,包含一些非常规和不必要的限制,使其在MIME之外无用.

我见过的许多CSV模块都不能容纳的事实是,在一个字段中可以编码多行,这意味着你不能假设每一行都是一个单独的记录,你要么不允许你的新行数据或准备处理此事.


Joe*_*ips 39

在字符串周围加上双引号.这通常是Excel的作用.

Ala Eli,

你用两个双引号来逃避双引号.例如"test1","foo""bar","test2"


Ada*_*icz 9

你可以在字段周围加上双引号.我不喜欢这种方法,因为它增加了另一个特殊字符(双引号).只需定义一个转义字符(通常是反斜杠)并在任何需要转义的地方使用它:

data,more data,more data\, even,yet more

您不必尝试匹配引号,并且您解析的异常更少.这也简化了您的代码.

  • 快速而脏,但如果您实际上有一个包含"\"的条目,则不起作用 (3认同)
  • Sarp,这就是为什么双 \\ 是转义反斜杠的原因,因为它现在变成了另一个特殊字符。 (2认同)
  • 这有效,但不是 CSV。它是 [一个 DSV](http://www.faqs.org/docs/artu/ch05s02.html#id2901882)。 (2认同)

Nik*_*nte 7

通过nuget可以使用一个库来处理几乎任何格式良好的CSV(.net) - CsvHelper

映射到类的示例:

var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>();
Run Code Online (Sandbox Code Playgroud)

读取单个字段的示例:

var csv = new CsvReader( textReader );
while( csv.Read() )
{
    var intField = csv.GetField<int>( 0 );
    var stringField = csv.GetField<string>( 1 );
    var boolField = csv.GetField<bool>( "HeaderName" );
}
Run Code Online (Sandbox Code Playgroud)

让客户端驱动文件格式:
,是标准字段分隔符,"是用于转义包含分隔符,引号或行结尾的字段的标准值.

要使用(例如)#字段和'转义:

var csv = new CsvReader( textReader );
csv.Configuration.Delimiter = "#";
csv.Configuration.Quote = ''';
// read the file however meets your needs
Run Code Online (Sandbox Code Playgroud)

更多文档

  • 如果你包含一个如何使用`CsvHelper`库来解决OP问题的例子,那就更好了. (3认同)

Bas*_*i M 6

如果您在*nix-system 上,可以访问sed并且仅在CSV特定字段中可能有一个或多个不需要的逗号,您可以使用以下单行将它们"作为RFC4180 部分括起来2提议:

sed -r 's/([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*)/\1"\2"\3/' inputfile
Run Code Online (Sandbox Code Playgroud)

根据不需要的逗号可能在哪个字段中,您必须更改/扩展正则表达式(和替换)的捕获组。
上面的示例将用引号将第四个字段(共六个)括起来。

在此处输入图片说明

--in-place-option结合使用,您可以将这些更改直接应用于文件。

为了“构建”正确的正则表达式,需要遵循一个简单的原则:

  1. 对于 CSV 中带有不需要的逗号的字段之前的每个字段,您编写一个[^,]*,并将它们放在一个捕获组中。
  2. 对于包含不需要的逗号的字段,您会写(.*).
  3. 对于带有不需要的逗号的字段之后的每个字段您写一个,.* 并将它们全部放在一个捕获组中。

以下是根据特定领域的不同可能的正则表达式/替换的简短概述。如果未给出,则替换为\1"\2"\3

([^,]*)(,.*)                     #first field, regex
"\1"\2                           #first field, substitution

(.*,)([^,]*)                     #last field, regex
\1"\2"                           #last field, substitution


([^,]*,)(.*)(,.*,.*,.*)          #second field (out of five fields)
([^,]*,[^,]*,)(.*)(,.*)          #third field (out of four fields)
([^,]*,[^,]*,[^,]*,)(.*)(,.*,.*) #fourth field (out of six fields)
Run Code Online (Sandbox Code Playgroud)

如果要删除不需要的逗号sed而不是用引号将它们括起来,请参阅此答案


小智 5

添加对 Microsoft.VisualBasic 的引用(是的,它说的是 VisualBasic,但它也适用于 C# - 请记住,最后它只是 IL)。

使用Microsoft.VisualBasic.FileIO.TextFieldParser该类解析 CSV 文件这里是示例代码:

 Dim parser As TextFieldParser = New TextFieldParser("C:\mar0112.csv")
 parser.TextFieldType = FieldType.Delimited
 parser.SetDelimiters(",")      

   While Not parser.EndOfData         
      'Processing row             
      Dim fields() As String = parser.ReadFields         
      For Each field As String In fields             
         'TODO: Process field                   

      Next      
      parser.Close()
   End While 
Run Code Online (Sandbox Code Playgroud)


Ruf*_*ock 5

您可以使用替代的“分隔符”,如“;” 或“|” 但最简单的可能只是引用大多数(体面的)CSV 库和大多数体面的电子表格支持的引用。

有关CSV 分隔符的更多信息以及用于描述分隔符和引用的标准格式的规范,请参阅此网页


Mik*_*Dub 5

正如我在harpo答案中的评论中所提到的那样,他的解决方案很好,并且在大多数情况下都有效,但是在某些情况下,当逗号彼此直接相邻时,逗号就无法拆分。

这是因为正则表达式字符串意外地充当了Vertabim字符串。为了使此行为正确无误,需要手动对正则表达式字符串中的所有字符进行转义,而无需使用vertabim转义符。

就是 正则表达式应该使用手动转义符来实现:

",(?=(?:[^\"\"]*\"\"[^\"\"]*\"\")*(?![^\"\"]*\"\"))"

转化为 ",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"

使用vertabim字符串时,@",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))"其行为如下所示,您可以查看是否调试了正则表达式:

",(?=(?:[^"]*"[^"]*")*(?![^"]*"))"
Run Code Online (Sandbox Code Playgroud)

因此,总而言之,我推荐harpo的解决方案,但请注意这一小技巧!

我在CsvReader中包含了一些可选的故障保护功能,以在发生此错误时通知您(如果您具有预先知道的列数):

if (_expectedDataLength > 0 && values.Length != _expectedDataLength) 
throw new DataLengthException(string.Format("Expected {0} columns when splitting csv, got {1}", _expectedDataLength, values.Length));
Run Code Online (Sandbox Code Playgroud)

这可以通过构造函数注入:

public CsvReader(string fileName, int expectedDataLength = 0) : this(new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
    _expectedDataLength = expectedDataLength;
}
Run Code Online (Sandbox Code Playgroud)


H. *_*lyn 5

在欧洲,我们遇到这个问题肯定早于这个问题。在欧洲,我们使用逗号作为小数点。请参阅下面的数字:

| American      | Europe        |
| ------------- | ------------- |
| 0.5           | 0,5           |
| 3.14159265359 | 3,14159265359 |
| 17.54         | 17,54         |
| 175,186.15    | 175.186,15    |
Run Code Online (Sandbox Code Playgroud)

因此,无法对 CSV 文件使用逗号分隔符。因此,欧洲的 CSV 文件以分号( ;)分隔。

Microsoft Excel 等程序可以读取带有分号的文件,并且可以切换分隔符。您甚至可以使用制表符 ( \t) 作为分隔符。请参阅超级用户的回答


小智 5

这是一个巧妙的小解决方法:

\n\n

您可以使用希腊小写数字符号(U+0375)

\n\n

看起来像这样 \xcd\xb5

\n\n

使用这种方法也可以节省大量资源......

\n