这是一个有趣的问题:我有一个通用的价格文件,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脚本。
答案 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;
}
所以最后开头的锚定部分不包含逗号,但它们之间的中间字段可以。