пропуская первые x и последние y строк файла - PullRequest
3 голосов
/ 18 октября 2019

Я делаю простой анализ текстовых файлов (которые могут попасть в диапазон 1 ГБ). Как бы я пропустил первые N строк и, что более важно, последние (разные) N строк? Я уверен, что смогу открыть файл, сосчитать строки и что-то сделать с $ _

Ответы [ 2 ]

7 голосов
/ 18 октября 2019

Файл представляет собой последовательность байтов без понятия «строки». Некоторые из этих байтов рассматриваются как разделители строк (перевод строки), и именно так программное обеспечение дает нам возможность работать с нашими «логическими» строками. Таким образом, невозможно узнать, сколько строк в файле - не прочитав и не посчитав их, то есть.

Простой и наивный способ - читать построчно и считать

open my $fh, '<', $file  or die "Can't open $file: $!";

my $cnt;
++$cnt while <$fh>;

с немного более быстрой версией с использованием $. переменная

1 while <$fh>;
my $cnt = $.;

Это занимает от 2,5 до 3 секунд для текстового файла объемом 1,1 Гб на приемлемом рабочем столе.

Мы можем значительно ускорить это, читая большие куски и считая символы новой строки

open my $fh, '<', $file  or die "Can't open $file: $!";

my $cnt; 
NUM_LINES: {
    my $len = 64_000; 
    my $buf;

    $cnt += $buf =~ tr/\n// 
        while read $fh, $buf, $len;

    seek $fh, 0, 0;
};

Это занимает чуть более полсекунды, на том же оборудовании и версиях Perl.

Я поместил его в блок, чтобы охватить ненужные переменные, но он должен быть в подпрограмме, где вы можете проверить, где находится дескриптор файла, когда вы его получите, и вернуть его после подсчета (чтобы мы могли посчитать«остаток» строк из некоторой точки в файле, после чего обработка может быть продолжена) и т. д. Он также должен включать проверки операции read при каждом вызове.

Я бы подумал, что полсекунды на больших файлах Gb совсем не так уж и плохо.

Тем не менее, вы можете пойти еще быстрее, за счет того, что это намного грязнее. Получите размер файла (метаданные, поэтому чтение не требуется) и seek в положение, которое оценивается как требуемое количество строк до конца (чтение не требуется). Это, скорее всего, не попадет в нужное место, поэтому прочитайте до конца, чтобы пересчитать строки и скорректировать, ища назад (дальше или ближе). Повторяйте до тех пор, пока не достигнете нужного места.

open my $fh, "<", $file; 
my $size = -s $file;

my $estimated_line_len = 80;
my $num_last_lines     = 100;

my $pos = $size - $num_last_lines*$estimated_line_len;

seek $fh, $pos, 0; 

my $cnt;    
++$cnt while <$fh>; 

say "There are $cnt lines from position $pos to the end"; 

# likely need to seek back further/closer ...

Я предполагаю, что это должно привести вас туда менее чем за 100 мс. Обратите внимание, что $pos, скорее всего, находится внутри строки.

Затем, как только вы узнаете количество строк (или позицию для нужного количества строк до конца), выполните seek $fh, 0, 0 и обработайте. Или действительно иметь это в подпрограмме, которая возвращает дескриптор файла туда, где он был до возврата, как уже упоминалось.

3 голосов
/ 18 октября 2019

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

skip-first-last.pl

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

my ($first, $last) = @ARGV;

my @buf;
while (<STDIN>) {
    my $mod = $. % $last;
    print $buf[$mod] if defined $buf[$mod];
    $buf[$mod] = $_ if $. > $first;
}

1;

Пропустить сначала 5строки и последние 2 строки:

$ cat -n skip-first-last.pl | ./skip-first-last.pl 5 2
     6
     7  my @buf;
     8  while (<STDIN>) {
     9      my $mod = $. % $last;
    10      print $buf[$mod] if defined $buf[$mod];
    11      $buf[$mod] = $_ if $. > $first;
    12  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...