如何在CSV文件中处理描述中的逗号

时间:2013-06-21 15:46:46

标签: perl csv comma

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

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

这是文件的样子。

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脚本。

5 个答案:

答案 0 :(得分:6)

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

在这种情况下:

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

反过来,如果你碰巧有“角色里面的你用\来逃避它(那么你用普通的方式做)。

答案 1 :(得分:1)

所以,你有一些模糊地类似于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,但你也需要改变你的差距。这就是为什么CSV不好。

答案 2 :(得分:0)

根据你在 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

答案 3 :(得分:0)

怎么样:

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

答案 4 :(得分:0)

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

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

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