为制表符分隔文件中的每个列执行不同的正则表达式

时间:2013-02-05 01:28:29

标签: linux perl bash unix

我发现自己在大约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替换0但不用0替换0用于列C等等我还有另外需要的问题例如,可能为列E运行备用正则表达式。

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

任何帮助都将不胜感激,如果你解决了,我会以10美元的价格为你选择的饮料付款。

6 个答案:

答案 0 :(得分:1)

使用诸如Text::CSV之类的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),并且它不会替换数字旁边的零,这对于某些数字会失败,例如{{ 1}}或20.0

<强>更新

如果你想根据列名称而不是位置进行替换,事情会变得复杂一些。但是,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

答案 1 :(得分: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

答案 2 :(得分: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];
}

答案 3 :(得分:0)

我可能会在'autosplit'模式下使用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'变成'20鳟鱼';如果需要,你可以使它更复杂。

样本数据输出:

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

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

答案 4 :(得分: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

答案 5 :(得分: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 ...
>