3

这是一个有趣的问题:我有一个包含 ID#、描述和价格的通用价格文件,该文件以逗号分隔文件(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 脚本。

4

5 回答 5

6

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

在这种情况下:

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

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

于 2013-06-21T15:49:36.163 回答
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,但您还需要更改您的 gap-closer。这就是 notCSV 不好的原因。

于 2013-06-22T04:02:25.030 回答
0

how about this :

 $x='3456,Bad Part,with a comma,4.56';
 @y = split(/,/,$x);
 if ( $#y == 3 ) { 
    $desc = "$y[1],$y[2]";
 };   
于 2013-06-21T19:16:09.350 回答
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
于 2013-06-21T17:20:07.813 回答
0

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

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

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

于 2013-06-21T20:16:51.393 回答