如何使用正则表达式执行CSV文件的转换?

时间:2013-01-25 18:36:06

标签: regex perl csv sed awk

我试图暂时解决一个问题,但没有成功。一开始它看起来像一个微不足道的问题,但我已经堆积了它......

无论如何,我需要解决以下问题。我有一个非常大的CSV文件,其中包含以下格式的行:

NUMBER(9);NUMBER(1);NUMBER(9-10);NUMBER(2);NUMBER(1);...;NUMBER(2);NUMBER(1);STRING;DATE(DD.MM.YYYY);NUMBER(1351)

例如:

517755369;1;0001303717;48;1;63;8;50;2;51;6;53;7;55;3;57;4;59;5;;;;;CALL;07.12.2012;1351

在第一个树字段后的每一行中,我有1到10对NUMBER(2);NUMBER(1),然后是另外三个字段STRING;DATE(DD.MM.YYYY);NUMBER(1351)

我需要使用以下结构在文件中转换该文件:

517755369;1;0001303717;48;1;CALL;07.12.2012;1351
517755369;1;0001303717;63;8;CALL;07.12.2012;1351
517755369;1;0001303717;50;2;CALL;07.12.2012;1351
517755369;1;0001303717;51;6;CALL;07.12.2012;1351
517755369;1;0001303717;53;7;CALL;07.12.2012;1351
517755369;1;0001303717;55;3;CALL;07.12.2012;1351
517755369;1;0001303717;57;4;CALL;07.12.2012;1351
517755369;1;0001303717;59;5;CALL;07.12.2012;1351`

因此,输入文件中的每一行都应该转换为与原始行具有NUMBER(2);NUMBER(1)对的行数。

以下是输入文件的示例:

517760344;2;000601301061;31;1;;;;;;;;;;;;;;;;;;;CALL;07.12.2012;1351
518855369;1;000601303717;48;1;63;8;50;2;51;6;53;7;55;3;57;4;59;5;;;;;CALL;07.12.2012;1351
519775067;1;000601300771;4;2;6;3;19;1;;;;;;;;;;;;;;;CALL;07.12.2012;1351
617773407;1;000603252922;13;1;17;2;27;3;;;;;;;;;;;;;;;CALL;07.12.2012;1351
717764779;1;000601304021;31;1;;;;;;;;;;;;;;;;;;;CALL;07.12.2012;1351`

一般情况下,我需要一些 regexp ,我可以使用 sed awk (或一些 perl 脚本我可以对输入文件运行)。原始输入文件大约有1-1.5M条记录。此任务应尽快完成(转换最多5分钟)。

由于

4 个答案:

答案 0 :(得分:2)

也许以下内容会有所帮助:

use strict;
use warnings;

while (<>) {
    chomp;
    print +( join ';', ( split /;/ )[ 0 .. 4, -3 .. -1 ] ) . "\n";

}

您的数据输出:

517760344;2;000601301061;31;1;CALL;07.12.2012;1351
518855369;1;000601303717;48;1;CALL;07.12.2012;1351
519775067;1;000601300771;4;2;CALL;07.12.2012;1351
617773407;1;000603252922;13;1;CALL;07.12.2012;1351
717764779;1;000601304021;31;1;CALL;07.12.2012;1351

用法:perl file.csv >out.csv

您似乎想要前五个字段和后三个字段。以上split位于;上,join位于;,然后打印修改后的记录。

答案 1 :(得分:2)

来自@Kenosis的想法,但对规格的不同解释:

use strict;
use warnings;

while (<DATA>) {
    chomp;
    my @fields = split /;/;
    my $f = 3;
    while ($fields[$f]) {
      print join( ';', @fields[0 .. 2, $f, $f + 1, -3 .. -1]), "\n";
      $f += 2;
    }
}

__DATA__
517760344;2;000601301061;31;1;;;;;;;;;;;;;;;;;;;CALL;07.12.2012;1351
518855369;1;000601303717;48;1;63;8;50;2;51;6;53;7;55;3;57;4;59;5;;;;;CALL;07.12.2012;1351
519775067;1;000601300771;4;2;6;3;19;1;;;;;;;;;;;;;;;CALL;07.12.2012;1351
617773407;1;000603252922;13;1;17;2;27;3;;;;;;;;;;;;;;;CALL;07.12.2012;1351
717764779;1;000601304021;31;1;;;;;;;;;;;;;;;;;;;CALL;07.12.2012;1351

输出:

perl 14528210.pl
517760344;2;000601301061;31;1;CALL;07.12.2012;1351
518855369;1;000601303717;48;1;CALL;07.12.2012;1351
518855369;1;000601303717;63;8;CALL;07.12.2012;1351
518855369;1;000601303717;50;2;CALL;07.12.2012;1351
518855369;1;000601303717;51;6;CALL;07.12.2012;1351
518855369;1;000601303717;53;7;CALL;07.12.2012;1351
518855369;1;000601303717;55;3;CALL;07.12.2012;1351
518855369;1;000601303717;57;4;CALL;07.12.2012;1351
518855369;1;000601303717;59;5;CALL;07.12.2012;1351
519775067;1;000601300771;4;2;CALL;07.12.2012;1351
519775067;1;000601300771;6;3;CALL;07.12.2012;1351
519775067;1;000601300771;19;1;CALL;07.12.2012;1351
617773407;1;000603252922;13;1;CALL;07.12.2012;1351
617773407;1;000603252922;17;2;CALL;07.12.2012;1351
617773407;1;000603252922;27;3;CALL;07.12.2012;1351
717764779;1;000601304021;31;1;CALL;07.12.2012;1351

答案 2 :(得分:1)

这可能适合你(GNU sed):

sed -r 's/^(([^;]*;){3})(([0-9]+;){2})(([0-9]*;)*)(([^;]*;?){3})$/\1\3\7\n\1\5\7/;Ta;P;:a;D' file
  • s/^(([^;]*;){3})(([0-9]+;){2})(([0-9]*;)*)(([^;]*;?){3})$/\1\3\7\n\1\5\7/此替换命令构造两个字符串。第一个是预期的字符串,后跟换行符,第二个是原始字符串,而不是第一对数字。因此,^(([^;]*;){3})代表前三个字段,(([0-9]+;){2})表示第一对数字,(([0-9]*;)*)表示剩余的数字对,(([^;]*;?){3})$代表最后三个字段。
  • Ta如果替换命令失败,则跳转到标签a
  • P打印到模式空间中的第一个换行符。
  • :a标签a
  • D删除并包含第一个换行符并开始下一个循环。在模式空间为空之前,请勿读取其他行。

因此,实质上,s/.../.../D命令用于调用循环,该循环打印构造的字符串,直到替换命令失败然后结束循环。剩余的字符串将被完全删除,并开始下一行。

答案 3 :(得分:1)

以下是使用Text::CSV模块的单线程形式的解决方案。

perl -MText::CSV -lwe '$c = Text::CSV->new({
    sep_char=>';',
    eol=>$/
});                  
while($r = $c->getline(*STDIN)) { 
    my @a = splice @$r,0,3;            # remove 3 first elements
    my @c = splice @$r,-3;             # remove 3 last elements
    @$r = grep $_ ne '', @$r;          # remove empty elements
    while(@$r) {                       # while array is not empty
        $c->print(*STDOUT, [@a, splice(@$r,0,2),@c]);    # print all elements
    } }"

<强>输出:

517755369;1;0001303717;48;1;CALL;07.12.2012;1351
517755369;1;0001303717;63;8;CALL;07.12.2012;1351
517755369;1;0001303717;50;2;CALL;07.12.2012;1351
517755369;1;0001303717;51;6;CALL;07.12.2012;1351
517755369;1;0001303717;53;7;CALL;07.12.2012;1351
517755369;1;0001303717;55;3;CALL;07.12.2012;1351
517755369;1;0001303717;57;4;CALL;07.12.2012;1351
517755369;1;0001303717;59;5;CALL;07.12.2012;1351

所以基本上,正如我在评论中所说,删除前3个元素和3个最后元素并存储在单独的数组中。删除空元素。绕过剩余的元素并根据需要进行打印。