【问题标题】:How can I extract columns from a fixed-width format in Perl?如何从 Perl 中的固定宽度格式中提取列?
【发布时间】:2009-09-29 20:02:29
【问题描述】:

我正在编写一个 Perl 脚本来运行并获取各种数据元素,例如:

1253592000
1253678400                 86400                 6183.000000
1253764800                 86400                 4486.000000 
1253851200  36.000000      86400                10669.000000
1253937600  0.000000       86400                 9126.000000
1254024000  0.000000       86400                 2930.000000
1254110400  0.000000       86400                 2895.000000
1254196800  0.000000                             8828.000000

我可以抓取这个文本文件的每一行没有问题。

我正在使用正则表达式来获取每个字段。一旦我将行放入变量中,即 $line - 我如何获取每个字段并将它们放入自己的变量中,即使它们具有不同的分隔符?

【问题讨论】:

  • 您能否更具体地了解您的分隔符是什么?
  • @Paul:这就是问题所在。此输出由应用程序 (splunk) 生成。我无法设置分隔符,它们似乎只是每列之间的两个空格字符 - 但有空格超出了那个范围。
  • @Greg:这看起来根本不像一个“分隔”文件。这些是固定宽度的列。下面 FM 的解决方案确实是解析这个的最干净的方法。
  • @Paul - 好的。谢谢。我总是指任何被某物隔开的东西,定界的。但这更有意义。
  • 我不确定我是否同意,一个很简单,这个意味着固定宽度格式用于数据库或更复杂的东西。

标签: perl fixed-width


【解决方案1】:

此示例说明了如何使用空格作为分隔符 (split) 或使用固定列布局 (unpack) 来解析行。对于unpack,如果您使用大写字母(A10 等),将为您删除空格。 注意:正如 brian d foy 指出的那样,split 方法不适用于缺少字段(例如第二行数据)的情况,因为字段位置信息会丢失; unpack 是这里的方法,除非我们误解了您的数据。

use strict;
use warnings;

while (my $line = <DATA>){
    chomp $line;
    my @fields_whitespace = split m'\s+', $line;
    my @fields_fixed = unpack('a10 a10 a12 a28', $line);
}

__DATA__
1253592000                                                  
1253678400                 86400                 6183.000000
1253764800                 86400                 4486.000000
1253851200 36.000000       86400                10669.000000
1253937600  0.000000       86400                 9126.000000
1254024000  0.000000       86400                 2930.000000
1254110400  0.000000       86400                 2895.000000
1254196800  0.000000                             8828.000000

【讨论】:

  • 大家都忘了 Perl 有包,但它真的很方便,我自己应该多用它。我只是在为下一版 Effective Perl Programming 编辑那一章。 :)
  • split m'\s+' 会更好地突出显示。
  • 根据 perldoc -“字符串被分成由 TEMPLATE 描述的块。”这些块被插入到@fields_fixed 数组中,对吗?
  • while ($line = ) { if ($x
【解决方案2】:

使用my module DataExtract::FixedWidth。在 perl 中使用 Fixed Width 列时,它是功能最齐全且经过良好测试的。如果这还不够快,您可以传入unpack_string 并消除对边界的启发式检测的需要。

#!/usr/bin/env perl
use strict;
use warnings;
use DataExtract::FixedWidth;
use feature ':5.10';

my @rows = <DATA>;
my $de = DataExtract::FixedWidth->new({
  heuristic => \@rows
  , header_row => undef
});

say join ('|',  @{$de->parse($_)}) for @rows;

    --alternatively if you want header info--

my @rows = <DATA>;
my $de = DataExtract::FixedWidth->new({
  heuristic => \@rows
  , header_row => undef
  , cols => [qw/timestamp field2 period field4/]
});

use Data::Dumper;
warn Dumper $de->parse_hash($_) for @rows;

__DATA__
1253592000
1253678400                 86400                 6183.000000
1253764800                 86400                 4486.000000
1253851200  36.000000      86400                10669.000000
1253937600  0.000000       86400                 9126.000000
1254024000  0.000000       86400                 2930.000000
1254110400  0.000000       86400                 2895.000000
1254196800  0.000000                             8828.000000

【讨论】:

  • 我以前用过这个模块,列检测很流畅。
【解决方案3】:

我不确定列名和格式,但您应该可以使用 Text::FixedWidth 调整此配方以适应您的喜好

use strict;
use warnings;
use Text::FixedWidth;

my $fw = Text::FixedWidth->new;
$fw->set_attributes(
    qw(
        timestamp undef  %10s
        field2    undef  %10s
        period    undef  %12s
        field4    undef  %28s
        )
);

while (<DATA>) {
    $fw->parse( string => $_ );
    print $fw->get_timestamp . "\n";
}

__DATA__
1253592000
1253678400                 86400                 6183.000000
1253764800                 86400                 4486.000000
1253851200 36.000000       86400                10669.000000
1253937600  0.000000       86400                 9126.000000
1254024000  0.000000       86400                 2930.000000
1254110400  0.000000       86400                 2895.000000
1254196800  0.000000                             8828.000000

【讨论】:

    【解决方案4】:

    您可以拆分线路。看来您的分隔符只是空格?您可以按照以下顺序执行操作:

    @line = split(" ", $line);
    

    这将匹配所有空格。然后,您可以通过 $line[0]、$line[1] 等进行边界检查和访问每个字段。

    Split 也可以使用正则表达式而不是字符串作为分隔符。

    @line = split(/\s+/, $line);
    

    这可能会做同样的事情。

    【讨论】:

    • 我认为他在谈论固定宽度编码。
    • 使用此方法 - 效果很好,输出:时间:1253592000 Livereporter:跨度:Bcreporter:时间:1253678400 Livereporter:86400 跨度:6183.000000 Bcreporter:时间:1253764800 Livereporter:864000 跨度:448。 1253851200 Livereporter:36.000000跨度:86400 Bcreporter:10669.000000时间:1253937600 Livereporter:0.000000跨度:86400 Bcreporter:9126.000000时间:1254024000 Livereporter:0.000000跨度:86400 Bcreporter:2930.000000时间:1254110400 Livereporter:0.000000跨度:86400 Bcreporter:2895.000000时间:1254196800 Livereporter :0.000000 跨度:8828.000000
    • 您不能在空白处拆分,因为有些字段是空的。执行此操作时会丢失列顺序。
    • 我将在几分钟后测试解包解决方案。 - 谢谢!
    【解决方案5】:

    如果所有字段都相同固定宽度并用空格格式化,您可以使用以下split

    @array = split / {1,N}/, $line;
    

    其中N 是字段的with。这将为每个空字段产生一个空间。

    【讨论】:

    • 我不认为你认为的那样。这一行有两个主要错误:一个在逻辑上,一个在语法上。
    • @brian d foy:谢谢,已修复。对不起,一个低质量的答案。无论如何,unpack 解决方案要好得多。
    【解决方案6】:

    固定宽度定界可以这样完成:

    my @cols;
    my %header;
    $header{field1} = 0; // char position of first char in field
    $header{field2} = 12;
    $header{field3} = 15;
    
    while(<IN>) {
    
       print chomp(substr $_, $header{field2}, $header{field3}); // value of field2 
    
    
    }
    

    我的 Perl 非常生锈,所以我确信那里存在语法错误。但这就是它的要点。

    【讨论】:

    • 你为什么要这样吃?你认为那会打印什么?请参阅 chomp 的文档以获取线索。不要太刻薄,但如果你在猜测并展示你从未尝试过甚至从未运行过的东西,你应该等待更有经验的人来回答。
    猜你喜欢
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 2017-03-30
    • 2014-02-10
    • 1970-01-01
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多