在 Perl 中,我如何解析 space 分隔的数据,其中还包含一个带有 space 的字段?

In Perl, how can I parse space delimited data which also include a field with spaces?

当我运行下面的代码时,我得到了错误"Use of uninitialized value in subroutine entry at ./test.pl line 20."。

输入

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 个由 space 分隔的字段必须按原样打印。但是,如您所见,在 wards 的第 5 个字段中,可以有任意数量的 space 分隔词。我想将它们组合在一起作为第 5 个字段,直到找到一个数字作为下一个字段。在上面的示例输入中,我希望 "View impl details" 作为第 5 个字段,而不是 "view" 作为第 5 个,"impl" 作为第 6 个,细节作为第 7 个字段。第二行数据也是如此。我希望 "Assessment for next exam preview" 显示为第 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;

我想从文件中读取数千行,每行包含由 space 分隔的 50 多个字段。我正在阅读每一行,将数据按 space 作为分隔符开始。从 wards 的第 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 "[=13=]: open: $!";
    print "$text_cont";
close STDOUT;

没有你的来源信息,我无法确定,但我认为你这里可能有一个围栏 post 错误:

(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' 字段是列表中的“第 4 个”字段。除非你真的是来自字段 9-57

我不认为你这样做,因为即使你去掉样本行上的 'wrapping' - 你的 'subfields' 只有 51 个元素。这是你问题的根源。

您还应该注意 split 在任何空白处拆分。 因此你得到 @subfields 包含:

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

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

那么为什么不改为:

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

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

Deprecated function whose use raises a warning, and which is slated to be removed in a future Perl version. It is very similar to matching against qr/ ^ [[:digit:]]+ $ /x , which you should convert to use instead.

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

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

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

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

我建议您检查 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);

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

If LIMIT is specified and positive, it represents the maximum number of fields into which the EXPR may be split; in other words, LIMIT is one greater than the maximum number of times EXPR may be split.

问题的原始措辞似乎暗示下面称为 $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;
}

我冒昧地给初始字段命名。