#perl
#perl
Вопрос:
Каким был бы наиболее эффективный способ чтения начала и конца огромного файла (двоичного или текстового) в заданном количестве байтов?
Пример:
=head2 read_file_contents(file, limit)
Given a filename, returns its partial content in bytes, with number of truncated bytes
=cut
sub read_file_contents
{
my ($file, $limit) = @_;
my $rv;
# Starting and ending number of bytes to read
$limit = $limit / 2;
# Reading beginning of file
my $start;
# code goes here
# Reading end of a file
my $end;
# code goes here
$rv = $start . "nnn truncated N bytes of data nnn" . $end;
return $rv;
}
Основная цель — иметь возможность быстро, без обработки всего файла, эффективно извлекать его начальные и конечные байты. Не проблема прочитать весь файл, а затем substr
выполнить его нужным способом, но он не будет нормально работать с файлами размером 10 ГБ .
Любые решения будут оценены.
Комментарии:
1. Примечание: ни у StackOverflow, ни у других сообществ не было четких ответов на знакомые вопросы, поэтому речь идет скорее о создании хорошего ответа, а не просто о поиске решения для себя.
Ответ №1:
open(my $fh, "<", $file) or die "...";
my $r = read($fh, $start, $limit) or die "...";
die "short readn" unless $r == $limit;
seek($fh, -$limit, 2) or die "...";
$r = read($fh, $end, $limit) or die "...";
Комментарии:
1. Это был бы отличный ответ, если бы он включал краткое объяснение и, возможно, ссылку на perldoc.
2.
use Fcntl qw(SEEK_END)
тогдаseek($fh, -$limit, SEEK_END)
было бы лучше, чем жесткое кодирование2
там3. Спасибо, @DaveMitchell. Ни у StackOverflow, ни у других сообществ не было четких ответов на знакомые вопросы, поэтому речь идет скорее о создании хорошего ответа, а не просто о поиске решения для себя. Как насчет подсчета количества усеченных байтов? Есть ли лучший способ, а не вычитание total-limit ? Не могли бы вы, пожалуйста, уточнить это до более удобного форматированного / объясненного ответа?
4. Downvoe из-за магических чисел. Был бы рад перейти на upvote, когда это будет исправлено.
Ответ №2:
Спасибо @DaveMitchell за понимание. Спасибо @ikegami за полезные советы. Это то, к чему я в итоге пришел.
Это может быть полезно для хвостирования журналов (возврата обратных выходных данных) или эффективного предварительного просмотра файлов любого размера.
Пример:
use Fcntl qw(SEEK_END);
=head2 read_file_contents_limit(file, limit, [opts])
Given a filename, returns its partial content with limit in bytes,
by default collected from both beginning and end of the file
* Options is a hash reference with
- [head] : Head the file only and just return beginning bytes
- [tail] : Tail the file only and return ending bytes
- [reverse] : Reverse output
- [nomessage] : Remove truncated message
=cut
sub read_file_contents_limit
{
my ($file, $limit, $opts) = @_;
my $data;
my $reverse = sub {
return join("n", reverse split("n", $_[0]));
};
my $nonulls = sub {
$_[0] =~ s/[^[:print:]nrt]/ /g;
return $_[0];
};
# Is binary file
my $binary = -B $file;
# Open file
open(my $fh, "<", $file) || return undef;
binmode $fh if ($binary);
# Get file size
my $fsize = -s $file;
# Return full file if requested limit fits the size
if ($fsize <= $limit) {
my $full;
read($fh, $full, $fsize);
$full = amp;$nonulls($full)
if ($binary);
$full = amp;$reverse($full)
if ($opts->{'reverse'});
return $full;
}
# Starting and ending number of bytes to read
my $split = !$opts->{'head'} amp;amp; !$opts->{'tail'};
$limit = $limit / 2 if ($split);
# Create truncated message
my $truncated = $fsize - $limit;
$truncated -= $limit if ($split);
$truncated = "nnn[--- truncated ${truncated} bytes of data ---]nnn";
$truncated = undef if ($opts->{'nomessage'});
# Reading beginning of file
my $head;
read($fh, $head, $limit);
# Return beginning only if requested
if ($opts->{'head'}) {
$head = amp;$nonulls($head)
if ($binary);
$head = amp;$reverse($head)
if ($opts->{'reverse'});
return $head . $truncated;
}
# Reading end of file
my $tail;
seek($fh, -$limit, SEEK_END);
read($fh, $tail, $limit);
# Return ending only if requested
if ($opts->{'tail'}) {
$tail = amp;$nonulls($tail)
if ($binary);
$tail = amp;$reverse($tail)
if ($opts->{'reverse'});
return $truncated . $tail;
}
# Return combined data
$data = $head . $truncated . $tail;
# Remove nulls for binary
$data = amp;$nonulls($data)
if ($binary);
# Reverse output if needed
$data = amp;$reverse($data)
if ($opts->{'reverse'});
return $data;
}
Пример того, как это можно использовать для хвостового файла журнала и отображения последних строк журнала вверху.
Использование:
say read_file_contents_limit('/var/webmin/miniserv.log', 2000, {'tail', 1, 'reverse', 1});
Вывод:
[--- truncated 1092091 bytes of data ---]
10.211.55.2 - root [14/Dec/2020:16:47:37 0000] "GET /favicon.ico HTTP/1.1" 200 15086
10.211.55.2 - root [14/Dec/2020:16:47:37 0000] "GET /debug.cgi HTTP/1.1" 200 3662
10.211.55.2 - root [14/Dec/2020:16:47:36 0000] "GET /favicon.ico HTTP/1.1" 200 15086
10.211.55.2 - root [14/Dec/2020:16:47:36 0000] "GET /debug.cgi HTTP/1.1" 200 3662
10.211.55.2 - root [14/Dec/2020:16:47:35 0000] "GET /favicon.ico HTTP/1.1" 200 15086
10.211.55.2 - root [14/Dec/2020:16:47:35 0000] "GET /debug.cgi HTTP/1.1" 200 3662
10.211.55.2 - root [14/Dec/2020:16:47:34 0000] "GET /favicon.ico HTTP/1.1" 200 15086
10.211.55.2 - root [14/Dec/2020:16:47:34 0000] "GET /debug.cgi HTTP/1.1" 200 3662
10.211.55.2 - root [14/Dec/2020:16:47:30 0000] "GET /favicon.ico HTTP/1.1" 200 15086
10.211.55.2 - root [14/Dec/2020:16:47:30 0000] "GET /debug.cgi HTTP/1.1" 200 3662
10.211.55.2 - root [14/Dec/2020:16:47:24 0000] "GET /favicon.ico HTTP/1.1" 200 15086
Комментарии:
1. Совет: следует использовать open(my $fh, «<:raw», $file)` для двоичных файлов
2. Совет: используйте
use Fcntl qw(SEEK_END);
иseek($fh, -$limit, SEEK_END)
вместо использования магических чисел.3. Спасибо, @ikegami! Однако
:raw
требуется только для Windows и ничего не делает в Linux? Кроме того, почему бы не использовать magic number? Предполагается ли, что оно изменено?4. Re raw: Это может иметь значение и в unix, и в каждой из моих программ. С таким же успехом можно привыкнуть делать это правильно. По крайней мере, это сигнализирует читателю о ваших намерениях. /// Re магические числа, читаемый код. Самое главное при программировании. Ваш код будет изменен, поддержан, отлажен, предоставлен общий доступ и т. Д. И для этого нужно уметь его читать. В общем, они часто могут меняться и могут отличаться в разных системах. (Я не думаю, что это относится конкретно к ним, но …?)
5. @ikegami — я собираюсь отредактировать свой ответ. Быстрый вопрос, хотя, говоря о
use Fcntl qw(SEEK_END);
— его следует использовать вне подраздела, в верхней части файла? Было бы нормально использовать его внутри вложенного файла, или это не имело бы смысла, посколькуuse
в любом случае вызывалось во время компиляции, или оно было бы ограничено выполнением вложенного файла, если оно помещено внутрь? Если он вызывается во время компиляции и не может быть использован внутри подпрограммы, можно ли использоватьeval "use Fcntl qw(SEEK_END);"
его и поместить его внутри подпрограммы? Что говорит об этом теория и лучшая практика?
Ответ №3:
Проверьте размер файла, затем выполните поиск ближе к концу…
Комментарии:
1. Спасибо — это скорее комментарий, а не ответ.
2. Нет, это ответ. Плохой, но он отвечает на вопрос.