在Perl中,如何解析空格分隔的数据,其中还包括带空格的字段?

时间:2015-07-29 08:45:52

标签: perl scripting

我收到错误“在./test.pl第20行的子程序条目中使用未初始化的值”。当我运行以下代码时。

INPUT

2015-05-01      abc     serv1   X       View impl details        34      33      2       0       1       0       4552    3312    0       72      0       0       0       0       0       0       0
       0       1       576     3       1       0       0       0       0       0       0       0       0       0.0     0       0       0       0       0       0       0       0       0       0       0       0
       0       1       381     671     1
2015-05-01      def   serv2   X       Assessment for next exam preview  22      22      0       0       1       0       1195    3577    0       3053    0       0       0       2       2       0
       0       0       26      163     10      2       0       0       0       0       0       0       0       0       0.0     0       0       0       0       0       0       0       0       0       0       0
       0       0       12      5       21      1

输出

由空格分隔的前4个字段必须按原样打印。但是,正如您所看到的,病房的第5个字段,可以有任意数量的空格分隔字。我想把它们作为第5场聚集在一起,直到找到一个数字作为下一个场。在上面的示例输入中,我希望“查看impl详细信息”为第5个字段而不是“视图”为第5个,“impl”为第6个,详细信息为第7个字段。第二行数据也是如此。我希望“下次考试预览评估”显示为第5场&其余的都是他们自己的领域。

#!/usr/bin/perl
use strict;
use warnings;
use POSIX;

my $i_file='../out/test.out';
my $o_file='../sql/test.out';

my $text_cont="";

open (FILE, $i_file) or die "Could not read from $i_file, program halting.";
    while(<FILE>) {
        (my $fl_1, my $fl_2, my $fl_3, my $fl_4, my @subfields) = split;
        my @join_fields;

        my $l=0;
        for (my $k=5; $k <= 53; $k++) {
            $join_fields[$l] = "";

            if(isdigit($subfields[$k])) {
                $join_fields[$l] = $subfields[$k];
                $l = $l + 1;
            }
            else {
                $join_fields[$l] = $join_fields[$l] . $subfields[$k];
            }
        }
    }
close FILE;

我想从文件中读取数千行,每行包含50个以空格分隔的字段。我正在读取每一行,按空格分割数据作为分隔符开始。从病房的第5个区域开始,直到我得到一个带有数字的字段,我想将字段附加到第5个字段。然后最后打印出输出。

我是Perl的新手。我对错误的理解是它无法找到“isdigit”的定义。但是,考虑到互联网上的一些解决方案,我使用了POSIX包。它似乎没有帮助。有人可以帮助我达到我的要求吗?

更新的脚本

#!/usr/bin/perl
use strict;
use warnings;

my $i_file='../out/test.out';
my $o_file='../sql/test.sql';

my $text_cont=" ";

open (FILE, $i_file) or die "Could not read from $i_file, program halting.";
    while(<FILE>) {
        (my $fl_1, my $fl_2, my $fl_3, my $fl_4, my @subfields) = split;
        my @join_fields;

        my $l=0;
        foreach my $k_val ( @subfields ) {
            #$join_fields[$l] = "";
            if ($k_val ne " ") {
                if ( $k_val =~ m/^\d+$/ ) {
                    $join_fields[$l] = $k_val;
                    $l = $l ++;
                }
                else {
                    my $temp = $join_fields[$l];
                    my $new_val = $temp.$k_val;
                    $join_fields[$l] = $new_val;
                }
                $text_cont = $text_cont."$join_fields[0]";
            }
        }
    }
close FILE;

open STDOUT, ">", $o_file or die "$0: open: $!";
    print "$text_cont";
close STDOUT;

3 个答案:

答案 0 :(得分:3)

如果这些确实是固定宽度的字段,在复制期间会被破坏。粘贴,你应该使用unpack。否则,您可以利用specify a limit when using split

这一事实
  

如果指定了LIMIT且为正,则表示EXPR可能被分割的最大字段数;换句话说,LIMITEXPR可能被分割的最大次数大一个。

问题的原始措辞似乎暗示第五个字段,下面称为$msg,从不包含数字。根据OP的评论显示至少有一行,其中字段包含文本WD25,我正在更新下面的模式,以便更容许该字段中的文本。

#!/usr/bin/env perl

use strict;
use warnings;

my $i_file = 'userpf.input';

open my $IN, '<', $i_file
    or die "Cannot open '$i_file': $!";

my @data;

while (my $line = <$IN>) {
    next unless $line =~ /\S/;
    my ($date, $type, $serv, $flag, $rest) = split ' ', $line, 5;
    my ($msg, $fields) = ($rest =~ /^ (.+?) \s+ ([0-9] .+) /x);
    push @data, [ $date, $type, $serv, $flag, $msg, split(' ', $fields) ];
}

for my $x (@data) {
    print "'$_'\n" for @$x;
}

我冒昧地给最初的字段命名。

答案 1 :(得分:1)

如果没有您的源信息,我无法确定,但您认为这里可能有一个围栏帖错误:

(my $fl_1, my $fl_2, my $fl_3, my $fl_4, my @subfields) = split;

for (my $k=5; $k <= 53; $k++) {
            if(isdigit($subfields[$k])) {

你将@subfields从5迭代到53.但是第一个'subfield'字段是列表中的'4th'字段。除非你真的是从字段9-57开始。

我不认为你这样做,因为即使你在样品线上取出“包装” - 你的“子字段”也只有51个元素。这是您的问题的根源。

您还应该注意,split在任何空格上都会分裂。 因此,您获得@subfields包含:

$VAR1 = [
          'View',
          'impl',
          'details',
          '34',
          '33',
          '2',

但是我建议你可能不想这样做 - 你只使用$k索引@subfields

那么为什么不呢:

foreach my $k_val ( @subfields ) { 
    if ( isdigit $k_val ) { 
         # etc... 
    }
}

但你也是对的 - 我收到警告isdigit已被弃用:

  

不推荐使用的函数,其使用会引发警告,并且将在未来的Perl版本中删除。它非常类似于匹配qr / ^ [[:digit:]] + $ / x,你应该转换为使用它。

有多种方法可以做类似的事情 - 我建议您可能需要:

if ( $k_val =~ m/^\d+$/ ) {

将使用正则表达式检查$k_val是否仅为数字(1个或更多数字字符)。

答案 2 :(得分:1)

根据我的理解你的要求,我修改了你的脚本。我已将输入记录分隔符$/\n修改为2015,因为您要处理的所需字符串由换行符分隔,虽然解决方案非常简单,但它可以正常工作:

我建议您检查File::Stream以使输入记录分隔符$/成为正则表达式,即值不是2015或其他内容。

#!/usr/bin/perl
use strict;
use warnings;

local $/="2015"; # set input record separator as 2015
open my $fh, '<','file' or die "unable to open file: $! \n";
my @subfields;
my $junk=<$fh>; # remove first one
while(<$fh>){
  chomp;  # remove 2015 from last
  $_= $junk.$_; # concatenate 2015 at begining of $_
  (my $fl_1, my $fl_2, my $fl_3, my $fl_4, my @subfields) = split;
    my @join_fields; 
    my $new_val="";
     foreach my $k_val ( @subfields ) {
      if ( $k_val =~ m/^\d+(.\d+)?$/ ) {
            push(@join_fields,$k_val);   
        }
      else{
          $new_val .= $k_val;
       }
    }

   push(@join_fields,$new_val);
   my $fl_5 = pop @join_fields; # pop out your fifth field here
   print "$fl_1 $fl_2 $fl_3 $fl_4 $fl_5 @join_fields \n";

}
close($fh);