【问题标题】:How can I break apart fixed-width columns in Perl?如何在 Perl 中拆分固定宽度的列?
【发布时间】:2010-12-26 00:22:21
【问题描述】:

编程对我来说太陌生了,我很抱歉不知道如何表达这个问题。

我有一个从内部工具获取变量的 Perl 脚本。这并不总是看起来的样子,但它总是遵循这种模式:

darren.local           1987    A      Sentence1
darren.local           1996    C      Sentence2
darren.local           1991    E      Sentence3
darren.local           1954    G      Sentence4
darren.local           1998    H      Sentence5

对于 Perl,将这些行中的每一行单独放入一个变量的最简单方法是什么?根据内部工具吐出的内容,每行总是不同的,可能超过五行。每行中的大写字母是它最终将被排序的内容(所有 As、所有 Cs、所有 Es 等)。我应该看正则表达式吗?

【问题讨论】:

  • 这些数据/行在哪里?您的内部工具是否将它们放入单个变量中?还是您需要读取的文件中的文本数据?
  • 该工具将它们放入一个变量中。
  • +1 为 Perl 新手 - SentenceX 是否意味着每行末尾都有一个多词句子?请记住,一个句子也会被 \s+ 分割

标签: perl


【解决方案1】:

我喜欢使用unpack 来处理这类事情。它快速、灵活且可逆。

您只需要知道每一列的位置,unpack 就可以自动修剪每一列中多余的空白。

如果您更改其中一列中的内容,很容易通过使用相同格式重新打包回到原始格式:

my $format = 'A23 A8 A7 A*';

while( <DATA> ) {
    chomp( my $line = $_ );

    my( $machine, $year, $letter, $sentence ) =
        unpack( $format, $_ );

    # save the original line too, which might be useful later
    push @grades, [ $machine, $year, $letter, $sentence, $_ ];
    }

my @sorted = sort { $a->[2] cmp $b->[2] } @grades;

foreach my $tuple ( @sorted ) {
    print $tuple->[-1];
    }

# go the other way, especially if you changed things
foreach my $tuple ( @sorted ) {
    print pack( $format, @$tuple[0..3] ), "\n";
    }

__END__
darren.local           1987    A      Sentence1
darren.local           1996    C      Sentence2
darren.local           1991    E      Sentence3
darren.local           1954    G      Sentence4
darren.local           1998    H      Sentence5

现在,还有一个额外的考虑因素。听起来您可能在单个变量中有这么多多行文本。通过在对标量的引用上打开文件句柄来处理它,就像处理文件一样。文件句柄负责其余部分:

 my $lines = '...multiline string...';

 open my($fh), '<', \ $lines;

 while( <$fh> ) {
      ... same as before ...
      }

【讨论】:

  • 一个可读 Perl 的好例子......(即使是每两年一次的用户)
  • 我不确定您看到的是哪种格式,因为我在发布的第一个格式中犯了一个错误,但我们最终采用了相同的格式。
  • -1:是的,它很快,但与使用 split 相比,它的编码也更多,因此更费力且更容易出错。除非真的有很多数据要提取,否则这对我来说似乎是过早的优化。
  • Adrian,我很抱歉让您对我不喜欢您的回答感到不安,但是您很难解释一次 unpack 调用比单个调用拆分。 unpack 更加灵活。我这里叫酸葡萄。
  • +1 很好的说明unpack,一个经常被忽视的工具。非常小的细节:如果需要完美的可逆性,你想使用a* 而不是A*。后者将删除尾随空格,这可能是不受欢迎的(例如,如果句子长度不同,但数据用户不希望在反向行程中出现锯齿状记录)。
【解决方案2】:
use strict;
use warnings;

# this puts each line in the array @lines
my @lines = <DATA>; # <DATA> is a special filehandle that treats
                    # everything after __END__ as if it was a file
                    # It's handy for testing things

# Iterate over the array of lines and for each iteration
# put that line into the variable $line
foreach my $line (@lines) {
   # Use split to 'split' each $line with the regular expression /s+/
   # /s+/ means match one or more white spaces.
   # the 4 means that all whitespaces after the 4:th will be ignored
   # as a separator and be included in $col4
   my ($col1, $col2, $col3, $col4) = split(/\s+/, $line, 4);

   # here you can do whatever you need to with the data
   # in the columns. I just print them out
   print "$col1, $col2, $col3, $col4 \n";
}


__END__
darren.local           1987    A      Sentece1
darren.local           1996    C      Sentece2
darren.local           1991    E      Sentece3
darren.local           1954    G      Sentece4
darren.local           1998    H      Sentece5

【讨论】:

    【解决方案3】:

    假设文本被放入单个变量 $info,那么您可以使用内部 perl 拆分函数将其拆分为单独的行:

    my @lines = split("\n", $info); 
    

    其中@lines 是您的行的数组。 "\n" 是换行符的正则表达式。您可以按如下方式遍历每一行:

    foreach (@lines) {
       $line = $_;
       # do something with $line....  
    }
    

    然后您可以在空白处分割每一行(正则表达式 \s+,其中 \s 是一个空白字符,+ 表示 1 次或多次):

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

    然后您可以通过其数组索引直接访问每个字段:$field[0]、$field[1] 等。

    或者,你可以这样做:

    ($var1, $var2, $var3, $var4) = split("\s+", $line);
    

    这会将每行中的字段放入单独的命名变量中。

    现在 - 如果您想按第三列中的字符排序您的行,您可以这样做:

    my @lines = split("\n", $info); 
    my @arr = ();    # declare new array
    
    foreach (@lines) {
       my @fields = split("\s+", $_);
       push(@arr, \@fields)    # add @fields REFERENCE to @arr 
    }
    

    现在你有了一个“数组数组”。这可以很容易地排序如下:

    @sorted = sort { $a->[2] <=> $b->[2] } @arr;
    

    它将按@fields 的第三个元素(索引2)对@arr 进行排序。

    编辑 2 要将具有相同第三列的行放入自己的变量中,请执行以下操作:

    my %hash = ();             # declare new hash
    
    foreach $line (@arr) {     # loop through lines
      my @fields = @$line;     # deference the field array
    
      my $el = $fields[2];     # get our key - the character in the third column
    
      my $val = "";
      if (exists $hash { $el }) {         # check if key already in hash
         my $val = $hash{ $el };        # get the current value for key
         $val = $val . "\n" . $line;    # append new line to hash value         
      } else {
         $val = $line;
      }
      $hash{ $el } = $val;         # put the new value (back) into the hash
    }
    

    现在您有了一个以第三列字符为键的散列,每个键的值是包含该键的行。然后,您可以遍历散列并打印出来或以其他方式使用散列值。

    【讨论】:

    • 如果您打算在这种情况下使用 split,请使用第三个参数来限制它返回的元素数量。如果最后一列有大量空白,您将丢失部分数据。
    • 感谢 Richard -- 每行都需要按大写字母分组。根据该查询的输出,我可以有多达 20 行或少至 2 行。带有“C”的行需要进入一个变量,带有“B”的行需要进入自己的变量,等等。这样行吗?
    • 使用我上面回答中的排序函数,您的数组将按字母数字顺序排序。所以“A”会首先出现,“B”接下来会出现,依此类推。如果您想将所有“A”行放入单个变量中,则(与任何编程问题一样)有多种可能性。您可以使用键控散列/映射,将字符“A”等作为键,其值可以是 a) 行数组或 b) 单个单行,您可以在找到后续行时将其附加到这些行上。有关使用哈希的教程,请参阅 cs.mcgill.ca/~abatko/computers/programming/perl/howto/hash/…>。
    • 编辑:我的意思是 b) 一个可以附加的字符串...(还没有弄清楚如何编辑 cmets)
    【解决方案4】:

    对于每一行文本,如下所示:

    my ($domain, $year, $grade, @text) = split /\s+/, $line;
    

    我对句子使用数组,因为不清楚结尾的句子是否有空格。然后,如果需要,您可以将 @text 数组加入到新字符串中。如果结尾的句子没有空格,那么您可以将@text 转换为$text。

    【讨论】:

    • 如果您打算在这种情况下使用 split,请使用第三个参数来限制它返回的元素数量。如果最后一列有大量空白,您将丢失部分数据。
    【解决方案5】:

    使用 CPAN 和我的模块 DataExtract::FixedWidth:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use DataExtract::FixedWidth;
    
    my @rows = <DATA>;
    
    my $defw = DataExtract::FixedWidth->new({ heuristic => \@rows, header_row => undef });
    
    use Data::Dumper;
    
    print Dumper $defw->parse( $_ ) for @rows;
    
    __DATA__
    darren.local           1987    A      Sentence1
    darren.local           1996    C      Sentence2
    darren.local           1991    E      Sentence3
    darren.local           1954    G      Sentence4
    darren.local           1998    H      Sentence5
    

    没有比这更简单的了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多