Каков наилучший способ поместить файл в строку в Perl?
Да, есть больше, чем один способ сделать это, но должен быть канонический, самый эффективный или самый лаконичный способ. Я добавлю ответы, которые я знаю, и посмотрю, что просачивается наверх.
Чтобы было ясно, вопрос в том, как лучше всего прочитать содержимое файла в строку. Одно решение на ответ.
18 ответов
Как насчет этого:
use File::Slurp;
my $text = read_file($filename);
ETA: примечание Ошибка #83126 для File-Slurp: дыра в безопасности с кодировкой (UTF-8). Теперь я рекомендую использовать File:: Slurper (заявление об отказе от ответственности: я его написал), также потому, что он лучше использует кодировки по умолчанию:
use File::Slurper 'read_text';
my $text = read_text($filename);
или путь:: крошечный:
use Path::Tiny;
path($filename)->slurp_utf8;
Мне нравится делать это с do
блок в котором я локализую @ARGV
так что я могу использовать оператор diamond, чтобы создать для меня магию файла.
my $contents = do { local(@ARGV, $/) = $file; <> };
Если вам нужно, чтобы это было немного более надежным, вы можете легко превратить это в подпрограмму.
Если вам нужно что-то действительно надежное, которое обрабатывает все виды особых случаев, используйте File:: Slurp. Даже если вы не собираетесь его использовать, посмотрите на источник, чтобы увидеть все дурацкие ситуации, с которыми он должен справиться. File:: Slurp имеет большую проблему безопасности, которая, похоже, не имеет решения. Частично это является неспособностью правильно обрабатывать кодировки. Даже мой быстрый ответ имеет эту проблему. Если вам нужно обработать кодировку (возможно, потому что вы не делаете все UTF-8 по умолчанию), это расширится до:
my $contents = do {
open my $fh, '<:encoding(UTF-8)', $file or die '...';
local $/;
<$fh>;
};
Если вам не нужно менять файл, вы можете использовать File:: Map.
При написании File:: Slurp (что является наилучшим способом), Ури Гуттман провел много исследований по многим способам выпекания и является наиболее эффективным. Он записал здесь свои выводы и включил в них информацию File::Slurp.
open(my $f, '<', $filename) or die "OPENING $filename: $!\n";
$string = do { local($/); <$f> };
close($f);
Что нужно подумать (особенно если сравнивать с другими решениями):
- Лексические файловые ручки
- Уменьшить область
- Уменьшить магию
Итак, я получаю:
my $contents = do {
local $/;
open my $fh, $filename or die "Can't open $filename: $!";
<$fh>
};
Я не большой поклонник магии <>, за исключением случаев, когда я использую магию <>. Вместо того, чтобы притворяться, почему бы просто не использовать открытый вызов напрямую? Это не намного больше работы, и это явно. (Истинная магия <>, особенно при работе с "-", гораздо больше работы для идеальной эмуляции, но мы все равно не используем ее здесь.)
mmap (отображение памяти) строк может быть полезно, когда вы:
- Есть очень большие строки, которые вы не хотите загружать в память
- Хотите слепо быструю инициализацию (вы получаете постепенный ввод / вывод при доступе)
- Есть случайный или ленивый доступ к строке.
- Может захотеть обновить строку, но только расширяет ее или заменяет символы:
#!/usr/bin/perl
use warnings; use strict;
use IO::File;
use Sys::Mmap;
sub sip {
my $file_name = shift;
my $fh;
open ($fh, '+<', $file_name)
or die "Unable to open $file_name: $!";
my $str;
mmap($str, 0, PROT_READ|PROT_WRITE, MAP_SHARED, $fh)
or die "mmap failed: $!";
return $str;
}
my $str = sip('/tmp/words');
print substr($str, 100,20);
Обновление: май 2012
Следующее должно быть довольно хорошо эквивалентно, после замены Sys::Mmap на File::Map
#!/usr/bin/perl
use warnings; use strict;
use File::Map qw{map_file};
map_file(my $str => '/tmp/words', '+<');
print substr($str, 100, 20);
Это не быстро и не зависит от платформы, и действительно зло, но оно короткое (и я видел это в коде Ларри Уолла;-):
my $contents = `cat $file`;
Дети, не делайте этого дома;-).
{
open F, $filename or die "Can't read $filename: $!";
local $/; # enable slurp mode, locally.
$file = <F>;
close F;
}
use IO::All;
# read into a string (scalar context)
$contents = io($filename)->slurp;
# read all lines an array (array context)
@lines = io($filename)->slurp;
Для однострочников обычно можно использовать -0
переключатель (с -n
) чтобы perl прочитал весь файл сразу (если файл не содержит нулевых байтов):
perl -n0e 'print "content is in $_\n"' filename
Если это бинарный файл, вы можете использовать -0777
:
perl -n0777e 'print length' filename
Посмотрите сводку Perl6:: Slurp, которая невероятно гибка и, как правило, делает все правильно, без особых усилий.
Никто ничего не сказал о read или sysread, так что вот простой и быстрый способ:
my $string;
{
open my $fh, '<', $file or die "Can't open $file: $!";
read $fh, $string, -s $file; # or sysread
close $fh;
}
Вот хорошее сравнение самых популярных способов сделать это:
http://poundcomment.wordpress.com/2009/08/02/perl-read-entire-file/
Кандидат на худший способ сделать это! (См. Комментарий.)
open(F, $filename) or die "OPENING $filename: $!\n";
@lines = <F>;
close(F);
$string = join('', @lines);
Настройте специальную переменную разделителя записей $/
undef $/;
open FH, '<', $filename or die "$!\n";
my $contents = <FH>;
close FH;
open(IN, "<$filename");
$contents = join('', <IN>);
close(IN);
Подробности:
<IN>
дескриптор файла возвращает список (он же массив) строк, если он назначен переменной/контексту списка.
join
принимает разделитель и список строк и возвращает строку, содержащую все строки, соединенные вместе. Источник: https://perldoc.perl.org/functions/join ).
open
с префиксом имени файла «<» открывает файл в режиме чтения.
Я часто использую конструкцию соединения для проглатывания однострочных строк, напримерperl -e '$_=join("",<>);s/multiline_regex/replacement_string/gms;print'
. Опции m/s поддерживают многострочные регулярные выражения, см. https://perldoc.perl.org/perlre .
# Takes the name of a file and returns its entire contents as a string.
sub getfile
{
my($filename) = @_;
my($result);
open(F, $filename) or die "OPENING $filename: $!\n";
while(<F>) { $result .= $_; }
close(F);
return $result;
}