1

大约 8 年来,我发现自己第一次编写 PERL,但我在一些本应简单的事情上遇到了困难。这是基本前提:

包含一百个左右字段的文件,其中 10 个有不正确的数据(O 是 0)

A   B   C   D    E  F   ... 
br0wn   red   1278076   0range   "20 tr0ut"   123 ...
Green   0range   90876   Yell0w   "18 Salm0n"   456   ...

我正在尝试编写程序来拆分字段,然后允许我在字段 A 上运行正则表达式以将 0 替换为 O 但不将 0 替换为 C 列的 O 等等,我还有一个额外的问题是可能需要运行例如,列 E 的替代正则表达式。

我能够通过 /t 拆分记录中的所有字段。我在格式化命令以遍历每个字段并根据该字段运行特定的正则表达式时遇到问题。

任何帮助将不胜感激,如果您解决了问题,我将向您支付 10 美元购买您选择的饮料。

4

6 回答 6

1

使用诸如此类的 csv 解析器Text::CSV并不复杂。这样的事情可能就足够了:

use strict;
use warnings;
use Text::CSV;

my $csv = Text::CSV->new({
        sep_char    => "\t",
        binary      => 1,
        eol         => $/,
});
while (my $row = $csv->getline(*DATA)) {
    tr/0/o/ for @{$row}[0, 1, 3];            # replace in cols A, B and D
    s/(?<!\d)0(?!\d)/o/g for @{$row}[4];     # replace in col E
    $csv->print(*STDOUT, $row);              # print the result
}


__DATA__
A   B   C   D   E   F
br0wn   red 1278076 0range  "20 tr0ut"  123
Green   0range  90876   Yell0w  "18 Salm0n" 456

输出:

A       B       C       D       E       F
brown   red     1278076 orange  "20 trout"      123
Green   orange  90876   Yellow  "18 Salmon"     456

请注意,我使用简单的正则表达式而不是音译(全局替换)处理了您的混合字符串(E 列),它根本不会替换数字旁边的零,这对于某些数字(例如20.0or )将失败0

更新:

如果您想根据列而不是位置进行替换,事情会变得有点复杂。不过,Text::CSV可以应付。

use strict;
use warnings;
use Text::CSV;

my @pure_text   = qw(A B D);
my @mixed       = qw(E);

my $csv = Text::CSV->new({
        sep_char    => "\t",
        binary      => 1,
        eol     => $/,
});

my $cols = $csv->getline(*DATA);              # read column names
$csv->print(*STDOUT, $cols);
$csv->column_names($cols);                    # set column names

while (my $row = $csv->getline_hr(*DATA)) {   # hash ref instead of array ref
    tr/0/o/ for @{$row}{@pure_text};          # substitution on hash slice
    s/(?<!\d)0(?!\d)/o/g for @{$row}{@mixed};
    my @row = @{$row}{@$cols};                # make temp array for printing
    $csv->print(*STDOUT, \@row);
}


__DATA__
A   B   C   D   E   F
br0wn   red 1278076 0range  "20 tr0ut"  123
Green   0range  90876   Yell0w  "18 Salm0n" 456

此代码是用于演示的独立代码。要在文件上尝试代码,请更改*DATA*STDIN并使用以下脚本:

perl script.pl < input.csv
于 2013-02-05T01:54:37.737 回答
0

我可能会在“自动拆分”模式下使用 Perl:

perl -a -p -F"\t" \
     -e '$F[0] =~ s/0/o/g;
         $F[1] =~ s/0/O/g;
         $F[3] =~ s/0/o/g;
         $F[4] =~ s/(\D)0(\D)/\1o\2/g;  # Or other more complex regex
         # ...                          # Other fields can be edited
         $_ = join("\t", @F);           # Reassign fields to $_
        ' data-file

将“ $F[4]20 tr0ut”更改为“20 trout”的正则表达式;如果需要,您可以使其更复杂。

样本数据的输出:

A       B       C       D       E       F       ...
brown   red     1278076 orange  "20 trout"      123     ...
Green   Orange  90876   Yellow  "18 Salmon"     456     ...

这确实假设了一个严格的制表符分隔的数据文件。如果您没有严格的制表符分隔数据,则包含空格的引号字符串会使事情复杂化;那时, Text::CSV 对阅读这些行很有吸引力。

于 2013-02-05T02:11:39.673 回答
0

这是使用数组引用和/或子例程进行简单配置的一种方式,然后替换发生:

use strict;
use warnings;

my @subst = ([
  ['this', 'that'],
  ['O', 1],
],[
  ['foo', 'boo'],
  sub {s/a.*//},
]);

sub mk_subst {
  my $list = shift;
  my ($this, $that) = eval { @$list };
  return $list unless defined $this;
  sub { s/\Q$this/$that/ };
}

my @all;
for my $set (@subst) {
  my @list = eval { @$set };
  unless (@list) {
    push @all, [ sub {} ];
    next;
  }
  my @re;
  for my $s (@list) {
    push @re, mk_subst($s);
  }
  push @all, \@re;
}

while (<DATA>) {
  chomp;
  my @list = split /\t/, $_, -1;
  for my $i (0..$#list) {
    for ($list[$i]) {
      for my $funcs ($all[$i]) {
        for my $f (@$funcs) {
          $f->();
        }
      }
    }
  }
  print join("\t", @list), "\n";
}

__DATA__
thisO   fooabca1234
abc 123fooabca1234
于 2013-02-05T02:23:02.897 回答
0

这是使用GNU awk. 只需将列名添加到BEGIN块中的数组中。在下面的示例中,将仅修改 A、C 和 E 列。像这样运行:

awk -f script.awk file

内容script.awk

BEGIN {
    FS=OFS="\t"

    a["A"]
    a["C"]
    a["E"]
}

{
    for (i=1;i<=NF;i++) {

        if ($i in a && NR==1) {
            b[i]
        }

        else if (i in b) {
            $i = gensub(/(^|[^0-9])0([^0-9]|$)/,"\\1o\\2", "g", $i)
        }
    }
}1

制表符分隔的结果:

A   B   C   D   E   F   ... 
brown   red 1278076 0range  "20 trout"  123 ...
Green   0range  90876   Yell0w  "18 Salmon" 456 ...

或者,这是单线:

awk 'BEGIN { FS=OFS="\t"; a["A"]; a["C"]; a["E"] } { for (i=1;i<=NF;i++) { if ($i in a && NR==1) b[i]; else if (i in b) $i = gensub(/(^|[^0-9])0([^0-9]|$)/,"\\1o\\2", "g", $i) } }1' file
于 2013-02-05T01:41:08.813 回答
0

创建一个子程序数组,例如:

my @fixer;
$fixer[0] = sub { $_[0] =~ s/0/o/; };
my @fields = split /\t/, $input;
for (my $i = 0; $i <= $#fields; $i++) {
   $fixer[$i]->($fields[$i]) if defined $fixer[$i];
}
于 2013-02-05T01:46:29.777 回答
0
perl -F -lane 'for(@F){$_=~s/0/o/g if(/0/ && /[a-zA-Z]+/);} print "@F"' your_file

在下面测试

> cat temp
br0wn   red   1278076   0range   "20 tr0ut"   123 ...
Green   0range   90876   Yell0w   "18 Salm0n"   456   ...

> perl -F -lane 'for(@F){$_=~s/0/o/g if(/0/ && /[a-zA-Z]+/);} print "@F"' temp
brown red 1278076 orange "20 trout" 123 ...
Green orange 90876 Yellow "18 Salmon" 456 ...
>
于 2013-02-05T06:56:48.733 回答