В Perl, как я могу прочитать весь файл в строку? - PullRequest
111 голосов
/ 05 июня 2009

Я пытаюсь открыть файл .html в виде одной большой длинной строки. Вот что у меня есть:

open(FILE, 'index.html') or die "Can't read file 'filename' [$!]\n";  
$document = <FILE>; 
close (FILE);  
print $document;

, что приводит к:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN

Однако я хочу, чтобы результат выглядел следующим образом:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"<br> "http://www.w3.org/TR/html4/loose.dtd"><br> <html><br> <head><br> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

Таким образом, я могу легче искать весь документ.

Ответы [ 17 ]

93 голосов
/ 05 июня 2009

Я бы сделал это так:

my $file = "index.html";
my $document = do {
    local $/ = undef;
    open my $fh, "<", $file
        or die "could not open $file: $!";
    <$fh>;
};

Обратите внимание на использование версии с тремя аргументами open. Это намного безопаснее, чем старые версии с двумя (или одним) аргументами. Также обратите внимание на использование лексического дескриптора файла. Лексические дескрипторы файлов по многим причинам более приятны, чем старые варианты без слов. Мы пользуемся одним из них здесь: они закрываются, когда выходят за рамки.

76 голосов
/ 05 июня 2009

Добавить:

 local $/;

перед чтением из дескриптора файла. См. Как я могу прочитать весь файл одновременно? или

$ perldoc -q "entire file"

См. Переменные, связанные с файловыми дескрипторами в perldoc perlvar и perldoc -f local.

Кстати, если вы можете разместить свой скрипт на сервере, вы можете иметь все нужные вам модули. См. Как мне сохранить каталог моего собственного модуля / библиотеки? .

Кроме того, Path :: Class :: File позволяет вам slurp и spew .

Path :: Tiny предоставляет еще больше удобных методов, таких как slurp, slurp_raw, slurp_utf8, а также их spew двойники.

75 голосов
/ 05 июня 2009

С Файл :: Slurp :

use File::Slurp;
my $text = read_file('index.html');

Да, даже вы можете использовать CPAN .

49 голосов
/ 05 июня 2009

Все посты немного не-идиоматичны. Идиома:

open my $fh, '<', $filename or die "error opening $filename: $!";
my $data = do { local $/; <$fh> };

В большинстве случаев нет необходимости устанавливать $ / в undef.

18 голосов
/ 05 июня 2009

С perlfaq5: Как я могу прочитать весь файл сразу? :


Вы можете использовать модуль File :: Slurp, чтобы сделать это за один шаг.

use File::Slurp;

$all_of_it = read_file($filename); # entire file in scalar
@all_lines = read_file($filename); # one line per element

Обычный подход Perl для обработки всех строк в файле состоит в том, чтобы делать это по одной строке за раз:

open (INPUT, $file)     || die "can't open $file: $!";
while (<INPUT>) {
    chomp;
    # do something with $_
    }
close(INPUT)            || die "can't close $file: $!";

Это гораздо эффективнее, чем считывание всего файла в память в виде массива строк и последующая обработка его по одному элементу за раз, что часто - если не почти всегда - неправильный подход. Всякий раз, когда вы видите, кто-то делает это:

@lines = <INPUT>;

Вы должны долго и усердно думать о том, зачем вам нужно загружать все сразу. Это просто не масштабируемое решение. Вам также может быть интереснее использовать стандартный модуль Tie :: File или привязки $ DB_RECNO модуля DB_File, которые позволяют привязать массив к файлу, чтобы при доступе к элементу массив фактически обращался к соответствующей строке в файле. .

Вы можете прочитать все содержимое файлового дескриптора в скаляр.

{
local(*INPUT, $/);
open (INPUT, $file)     || die "can't open $file: $!";
$var = <INPUT>;
}

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

$var = do { local $/; <INPUT> };

Для обычных файлов вы также можете использовать функцию чтения.

read( INPUT, $var, -s INPUT );

Третий аргумент проверяет размер байта данных в дескрипторе файла INPUT и считывает столько байтов в буфер $ var.

7 голосов
/ 05 июня 2009

Либо установите $/ на undef (см. Ответ jrockway), либо просто объедините все строки файла:

$content = join('', <$fh>);

Рекомендуется использовать скаляры для файловых дескрипторов в любой версии Perl, которая его поддерживает.

7 голосов
/ 05 июня 2009

Простой способ:

while (<FILE>) { $document .= $_ }

Другой способ - изменить разделитель входной записи "$ /". Вы можете сделать это локально в пустом блоке, чтобы избежать изменения глобального разделителя записей.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
4 голосов
/ 12 мая 2013

Другой возможный способ:

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;
3 голосов
/ 05 июня 2009

Вы получаете только первую строку от оператора diamond <FILE>, потому что вы оцениваете ее в скалярном контексте:

$document = <FILE>; 

В контексте списка / массива оператор diamond возвращает все строки файла.

@lines = <FILE>;
print @lines;
2 голосов
/ 20 февраля 2012

Это скорее совет о том, как НЕ сделать это. Я только что плохо нашел ошибку в довольно большом Perl-приложении. Большинство модулей имели свои собственные файлы конфигурации. Чтобы прочитать файлы конфигурации в целом, я нашел эту строку Perl где-то в Интернете:

# Bad! Don't do that!
my $content = do{local(@ARGV,$/)=$filename;<>};

Переназначает разделитель строк, как описано выше. Но он также переназначает STDIN.

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

Например, делая это:

use strict;
use warnings;

my $filename = 'some-file.txt';

my $content = do{local(@ARGV,$/)=$filename;<>};
my $content2 = do{local(@ARGV,$/)=$filename;<>};
my $content3 = do{local(@ARGV,$/)=$filename;<>};

print "After reading a file 3 times redirecting to STDIN: $.\n";

open (FILE, "<", $filename) or die $!;

print "After opening a file using dedicated file handle: $.\n";

while (<FILE>) {
    print "read line: $.\n";
}

print "before close: $.\n";
close FILE;
print "after close: $.\n";

Результат:

After reading a file 3 times redirecting to STDIN: 3
After opening a file using dedicated file handle: 3
read line: 1
read line: 2
(...)
read line: 46
before close: 46
after close: 0

Странно то, что счетчик строк $. увеличивается для каждого файла на единицу. Он не сбрасывается и не содержит количества строк. И он не сбрасывается в ноль при открытии другого файла, пока не будет прочитана хотя бы одна строка. В моем случае я делал что-то вроде этого:

while($. < $skipLines) {<FILE>};

Из-за этой проблемы условие было ложным, поскольку счетчик строки не был сброшен должным образом. Я не знаю, является ли это ошибкой или просто неправильным кодом ... Также не помогает вызов close; oder close STDIN;.

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

Три строки в начале можно заменить на:

my $content = do{local $/; open(my $f1, '<', $filename) or die $!; my $tmp1 = <$f1>; close $f1 or die $!; $tmp1};
my $content2 = do{local $/; open(my $f2, '<', $filename) or die $!; my $tmp2 = <$f2>; close $f2 or die $!; $tmp2};
my $content3 = do{local $/; open(my $f3, '<', $filename) or die $!; my $tmp3 = <$f3>; close $f3 or die $!; $tmp3};

, который правильно закрывает дескриптор файла.

...