Как разобрать несколько строк, файл фиксированной ширины в Perl? - PullRequest
4 голосов
/ 14 декабря 2011

У меня есть файл, который мне нужно проанализировать в следующем формате. (Все разделители являются пробелами):

field name 1:            Multiple word value.
field name 2:            Multiple word value along
                         with multiple lines.
field name 3:            Another multiple word
                         and multiple line value.

Я знаком с тем, как анализировать однострочный файл с фиксированной шириной, но не знаю, как обрабатывать несколько строк.

Ответы [ 4 ]

8 голосов
/ 14 декабря 2011
#!/usr/bin/env perl

use strict; use warnings;

my (%fields, $current_field);

while (my $line = <DATA>) {
    next unless $line =~ /\S/;

    if ($line =~ /^ \s+ ( \S .+ )/x) {
        if (defined $current_field) {
            $fields{ $current_field} .= $1;
        }
    }
    elsif ($line =~ /^(.+?) : \s+ (.+) \s+/x ) {
        $current_field = $1;
        $fields{ $current_field } = $2;
    }
}

use Data::Dumper;
print Dumper \%fields;

__DATA__
field name 1:            Multiple word value.
field name 2:            Multiple word value along
                         with multiple lines.
field name 3:            Another multiple word
                         and multiple line value.
4 голосов
/ 15 декабря 2011

Фиксированная ширина говорит мне unpack. Можно проанализировать регулярные выражения и разделить, но unpack должен быть более безопасным выбором, так как это правильный инструмент для данных фиксированной ширины.

Я установил ширину первого поля равным 12, а пустое пространство - между 13, что работает для этих данных. Возможно, вам придется изменить это. Шаблон "A12A13A*" означает «найти 12, затем 13 символов ascii, за которыми следует любая длина символов ascii». unpack вернет список этих совпадений. Кроме того, unpack будет использовать $_, если строка не указана, что мы и делаем здесь.

Обратите внимание, что если первое поле не имеет фиксированной ширины вплоть до двоеточия, как это показано в ваших данных образца, вам необходимо объединить поля в шаблоне, например "A25A *", а затем раздеть двоеточие.

Я выбрал массив в качестве устройства хранения, так как не знаю, уникальны ли имена ваших полей. Хеш будет перезаписывать поля с одинаковыми именами. Еще одно преимущество массива заключается в том, что он сохраняет порядок данных в том виде, в котором они отображаются в файле. Если эти вещи не имеют значения, и быстрый поиск является более приоритетным, используйте вместо этого хеш.

Код:

use strict;
use warnings;
use Data::Dumper;

my $last_text;
my @array;
while (<DATA>) {
    # unpack the fields and strip spaces
    my ($field, undef, $text) = unpack "A12A13A*";  
    if ($field) {   # If $field is empty, that means we have a multi-line value
            $field =~ s/:$//;             # strip the colon
        $last_text = [ $field, $text ];   # store data in anonymous array
        push @array, $last_text;          # and store that array in @array
    } else {        # multi-line values get added to the previous lines data
        $last_text->[1] .= " $text"; 
    }
}

print Dumper \@array;

__DATA__
field name 1:            Multiple word value.
field name 2:            Multiple word value along
                         with multiple lines.
field name 3:            Another multiple word
                         and multiple line value
                         with a third line

Выход:

$VAR1 = [
          [
            'field name 1:',
            'Multiple word value.'
          ],
          [
            'field name 2:',
            'Multiple word value along with multiple lines.'
          ],
          [
            'field name 3:',
            'Another multiple word and multiple line value with a third line'
          ]
        ];
2 голосов
/ 14 декабря 2011

Вы можете сделать это:

#!/usr/bin/perl

use strict;
use warnings;

my @fields;
open(my $fh, "<", "multi.txt") or die "Unable to open file: $!\n";

for (<$fh>) {
    if (/^\s/) {
        $fields[$#fields] .= $_;    
    } else {
        push @fields, $_;
    }
}

close $fh;

Если строка начинается с пробела, добавьте ее к последнему элементу в @fields, в противном случае вставьте ее в конец массива.

В качестве альтернативы, взломать весь файл и разделить с осмотром:

#!/usr/bin/perl

use strict;
use warnings;

$/=undef;

open(my $fh, "<", "multi.txt") or die "Unable to open file: $!\n";

my @fields = split/(?<=\n)(?!\s)/, <$fh>;

close $fh;

Хотя это не рекомендуемый подход.

0 голосов
/ 15 декабря 2011

Вы можете изменить разделитель:

$/ = "\nfield name";

while (my $line = <FILE>) {

    if ($line =~ /(\d+)\s+(.+)/) {
        print "Record $1 is $2";
    }
}  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...