【问题标题】:How to deal with a comma in description in a CSV file如何处理 CSV 文件中描述中的逗号
【发布时间】:2013-06-18 19:49:05
【问题描述】:

这是一个有趣的问题:我有一个包含 ID#、描述和价格的通用价格文件,它以逗号分隔文件(CSV 或 TSV)的形式从各种供应商处导入。其中一家供应商在其描述字段中使用逗号。问题在于导入认为每个逗号都定义了一个新列并丢弃了记录。 (如果导入文件是固定长度的,这很容易处理,可惜不是。)

问题:谁能想到如何处理Description 中的逗号?我想用句号或连字符替换逗号,这是可以接受的。

文件如下所示。

ID,Description,Price
1234,Good Part,1.23
2345,This is.ok,2.34
3456,Bad Part,with a comma,4.56

在第一条和第二条记录中,应该有 3 列。在第三个示例中,这会导致 4 列并抛出导入,因为它在第 3 列中查找货币,而是找到一个字符串。我大部分时间都在使用 Perl 和 Java 脚本。

【问题讨论】:

  • 您还应该向供应商提交一份错误报告,说明他们的 CSV 文件格式错误。

标签: perl csv comma


【解决方案1】:

最常见的解决方案是引用可能包含“坏字符”的字段。

在这种情况下:

3456,"Bad Part,with a comma",4.56

反过来,如果你碰巧有 " 字符,你用 \ 转义它(所以你用 plain )。

【讨论】:

  • 这可能行得通,但下一个障碍是:我如何以编程方式识别这两个部分在引号内是一起属于的???
  • 然后使用像Texr::CSV这样的正确解析器can handle this difficult but correct input
  • @Vongrats - 读取文件时?你不能。如果您知道数据的语义,您可以编写一些启发式方法(例如,第 3 列始终是浮点数,因此“with”不会是第 3 列的开头);但这在一般情况下不起作用。
  • @DVK :有一个想法。第三列始终是数字 INT 或浮点数。我会看看我能用它做什么。
  • @Vongrats - 只需计算每行中逗号的数量,然后您就可以计算出第二个字段的边界。
【解决方案2】:

所以,您有一些有点像 CSV 文件的东西,但不是。您可以做的一件事是缩小差距,然后正常处理它——其他人都建议了这样做的方法。您可以做的另一件事是耸耸肩并按原样处理它,而不是 CSV。

在这里,我们在行首有一个 ID,然后是 一个逗号。

/^(\d+),/;

然后是任何东西,后面跟一个逗号:

/^(\d+),(.+),/

然后是价格,然后是行尾:

/^(\d+),(.+),(\d+(?:\.\d+)?)$/

是的,中间的 (.+), 可以按照您的意愿使用 嵌入逗号。 + 是贪婪的,所以这从 从右到左找到允许其余部分的第一个点 要匹配的模式。

总共:

#! /usr/bin/env perl
use common::sense;

while (<DATA>) {
  next unless /^(\d+),(.+),(\d+(?:\.\d+)?)$/;
  say "ID: $1";
  say "Description: $2";
  say "Price: $3";
  say "----"
}

__DATA__
ID,Description,Price
1234,Good Part,1.23
2345,This is.ok,2.34
3456,Bad Part,with a comma,4.56

而且,更简洁一些(虽然名字比他们的名字长......):

#! /usr/bin/env perl
use common::sense;

while (chomp($_ = <DATA>)) {
  next if /
    ^ID,Description,Price\z  # allow only this header
    | ^\s*\z                 # and blank lines
    | ^\s*\#                 # and lines containing only a comment
  /xi;

  /^(?<ID> \d+),
    (?<Description> .+),
    (?<Price> \d+(?:\.\d+)?)
  \z/x or die "Invalid line: $_";

  say "$_: $+{$_}" for qw(ID Description Price);
  say "----";
}

__DATA__
ID,Description,Price
1234,Good Part,1.23
2345,This is.ok,2.34

# why do we allow this again?
id,description,price
3456,Bad Part,with a comma,4.56

两个输出:

ID: 1234
Description: Good Part
Price: 1.23
----
ID: 2345
Description: This is.ok
Price: 2.34
----
ID: 3456
Description: Bad Part,with a comma
Price: 4.56
----

是的,您需要更改此正则表达式以适应稍有不同的 notCSV,但您还需要更改您的 gap-closer。这就是 notCSV 不好的原因。

【讨论】:

    【解决方案3】:

    根据您在 depesz 的 回答中的评论,这是我尝试在双引号之间将该字段括起来的努力。然后只需使用Text::CSV_XS 或类似的来解析它。

    script.pl的内容:

    #!/usr/bin/env perl
    
    use warnings;
    use strict;
    
    my ($f, $num_fields_h);
    
    while ( <> ) { 
        chomp;
    
        ## Header:
        ## Get the position of the "Description" field and the total number
        ## of fields. I assume that header doesn't have the problem of commas
        ## in the middle.
        if ( $. == 1 ) { 
            my %h = do { my $i = 0; map { $_ => $i++ } split /,/ };
            $f = $h{ Description };
            $num_fields_h = (tr/,/,/) + 1;
            printf qq|%s\n|, $_; 
            next;
        }   
    
        ## Data lines:
        ## Split the line and join fields in three parts, the first one until the
        ## "Description" calculated in header. The second one from that position until
        ## the difference of fields between the header and this line. That number will
        ## be the number of commas in the description. The third one from that calculated
        ## position until the end.
        my @f = split /,/; 
        my $num_fields_d = (tr/,/,/) + 1;
        my $limit_description_field = $f + $num_fields_d - $num_fields_h;
        printf qq|%s\n|, 
            join q|,|, 
                @f[ 0 .. $f - 1 ],  
                q|"| . join( q|,|, @f[ $f .. $limit_description_field ] ) . q|"|, 
                @f[ ($limit_description_field + 1) .. $#f ];  
    }
    

    像这样运行它:

    perl script.pl infile
    

    产生:

    ID,Description,Price
    1234,"Good Part",1.23
    2345,"This is.ok",2.34
    3456,"Bad Part,with a comma",4.56
    

    【讨论】:

      【解决方案4】:

      这个怎么样:

       $x='3456,Bad Part,with a comma,4.56';
       @y = split(/,/,$x);
       if ( $#y == 3 ) { 
          $desc = "$y[1],$y[2]";
       };   
      

      【讨论】:

        【解决方案5】:

        如果你知道有多少个字段,并且信任除一个以外的所有字段,那么你可以从两端解析好的部分,剩下的就是坏字段;即

        while(<>){
         m/(^[^,]+),(.+),([^,]+$)/;
         my @fields = ($1,$2,$3);
         $fields[1]=~s/,/-/g;
        }
        

        所以开头和结尾的锚定部分不会包含逗号,但它们之间的中间字段可以。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-06-13
          • 2017-09-25
          • 2018-11-26
          • 2011-09-26
          • 1970-01-01
          • 2011-04-27
          相关资源
          最近更新 更多