Как в Perl преобразовать весь файл в строку?

118

Я пытаюсь открыть файл .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" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

Таким образом, мне будет проще искать по всему документу.

черт возьми
источник
8
На самом деле следует проверить, что такое определение «Невозможно установить», это общая проблема и обычно аргумент, который не нужно приводить. stackoverflow.com/questions/755168/perl-myths/…
Кент Фредрик
1
На самом деле я не могу ничего изменить на всем сервере, на котором работает этот сценарий, кроме самого сценария.
goddamnyouryan 05
Значит, вам не разрешено добавлять какие-либо файлы на сервер?
Брэд Гилберт
Модули FatPack в ваш скрипт? Кроме того, похоже, вы думаете о синтаксическом анализе HTML с помощью регулярных выражений, не надо.
MkV

Ответы:

82

Добавить:

 local $/;

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

$ perldoc -q "весь файл"

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

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

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

Путь :: Крошка дает еще более удобные методы , такие как slurp, slurp_raw,slurp_utf8 а также их spewколлеги.

Синан Унюр
источник
33
Вы, вероятно, должны объяснить, какие эффекты будет иметь локализация $ /, а также какова ее цель.
Дэнни
12
Если вы не собираетесь ничего объяснять по поводу локализации $/, вам, вероятно, следует добавить ссылки для получения дополнительной информации.
Брэд Гилберт
7
Хорошее пошаговое объяснение того, что происходит: {local $ /; <$ fh>} предоставляется здесь: perlmonks.org/?node_id=287647
dawez
Возможно, просто скажите, почему вы должны использовать, localа не my.
Geremia,
@Geremia Обсуждение области видимости выходит за рамки этого ответа.
Sinan Ünür
99

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

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

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

Чес. Owens
источник
9
Это, вероятно, лучший способ сделать это без cpan'd, поскольку он использует как три открытых аргумента, так и локализацию переменной INPUT_RECORD_SEPARATOR ($ /) для минимального необходимого контекста.
Дэнни
77

С File :: Slurp :

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

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

Quentin
источник
Оператор сказал, что он не может ничего изменить на сервере. Ссылка «Да, даже вы можете использовать CPAN» здесь показывает, как в большинстве случаев обойти это ограничение.
Трентон
Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Дмитрий
2
@Dmitry - Так что установите модуль. На странице метакпанма есть ссылка с инструкциями по установке, на которую я ссылался из этого ответа.
Квентин
53

Все сообщения немного не идиоматичны. Идиома такая:

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

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

jrockway
источник
3
local $foo = undefэто просто рекомендованный метод Perl Best Practice (PBP). Если мы публикуем фрагменты кода, я бы подумал, что сделать все возможное, чтобы прояснить это, было бы неплохо.
Дэнни
2
Показывать людям, как писать неидиоматический код - это хорошо? Если бы я увидел "local $ / = undef" в коде, над которым я работал, моим первым действием было бы публично унизить автора на irc. (И я вообще не разборчив в вопросах «стиля».)
jrockway
1
Хорошо, я укушу: что именно является ложным в "local $ / = undef"? Если ваш единственный ответ - «Это не идиоматично», то (а) я не так уверен и (б) и что? Я не так уверен, потому что это чертовски распространенный способ сделать это. И что, потому что это совершенно ясно и достаточно кратко. Вы можете быть более разборчивы в вопросах стиля, чем думаете.
Telemachus
1
Ключ в том, что «local $ /» является частью хорошо известной идиомы. Если вы пишете случайный код и пишете «local $ Foo :: Bar = undef;», это нормально. Но в этом особом случае вы могли бы с таким же успехом говорить на том же языке, что и все остальные, даже если он «менее ясен» (с чем я не согласен; поведение «локального» в этом отношении четко определено).
jrockway 08
11
Извините, не согласен. Когда вы хотите изменить фактическое поведение магической переменной, гораздо чаще выражаться явно; это заявление о намерениях. Даже в документации используется local $ / = undef (см. Perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Леонардо Эррера
19

Из 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.

Брайан Д Фой
источник
8

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

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

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

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
Питер Мортенсен
источник
1
С обоими приведенными вами примерами возникает значительное количество проблем. Основная проблема в том, что они написаны на древнем Perl, я бы рекомендовал прочитать Modern Perl
Брэд Гилберт
@Brad, комментарий был сделан много лет назад, но точка зрения остается неизменной. better is{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger
@ Джоэл, это ненамного лучше. Вы не проверили вывод openили неявно вызванный close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}, (Проблема в том, что кодировка ввода не указана.)
Брэд Гилберт
use autodie, главное улучшение, которое я хотел показать, - это лексический дескриптор файла и 3 аргумента open. Есть какая-то причина, по которой вы это делаете do? почему бы просто не выгрузить файл в переменную, объявленную перед блоком?
Joel Berger
7

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

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

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

Kixx
источник
4

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

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;
эхо
источник
3

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

$document = <FILE>; 

В контексте списка / массива оператор «ромб» вернет все строки файла.

@lines = <FILE>;
print @lines;
Натан
источник
1
Замечание по номенклатуре: оператор космического корабля есть, <=>а <>оператор ромба.
toolic 06
О, спасибо, я раньше не слышал «алмазного оператора» и подумал, что у них обоих одно и то же имя. Подправлю выше.
Натан
2

Я бы сделал это самым простым способом, чтобы любой мог понять, что происходит, даже если есть более разумные способы:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
Что-то что-то
источник
Все эти конкатенации строк будут довольно дорогими. Я бы этого не сделал. Зачем разрывать данные только для того, чтобы снова собрать их?
andru
2
open f, "test.txt"
$file = join '', <f>

<f>- возвращает массив строк из нашего файла (если $/имеет значение по умолчанию "\n") и затем join ''вставляет этот массив в.

Тима Епанчинцев
источник
2

Это скорее предложение, как этого НЕ делать. Мне просто не удалось найти ошибку в довольно большом 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};

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

челюсть
источник
2

использование

 $/ = undef;

раньше $document = <FILE>;. $/- разделитель входных записей , по умолчанию - новая строка. Переопределяя его на undef, вы говорите, что разделителя полей нет. Это называется режимом «отхлебки».

Другие решения, такие как undef $/и local $/(но не my $/) повторно объявляют $ / и, таким образом, производят тот же эффект.

Geremia
источник
0

Вы можете просто создать подпрограмму:

#Get File Contents
sub gfc
{
    open FC, @_[0];
    join '', <FC>;
}
Шелдон Юнкер
источник
0

Я не знаю, хорошая ли это практика, но я использовал это:

($a=<F>);
zawy
источник
-1

Все это хорошие ответы. НО, если вы чувствуете себя ленивым, и файл не такой уж большой, а безопасность не является проблемой (вы знаете, что у вас нет испорченного имени файла), вы можете выполнить раскладку:

$x=`cat /tmp/foo`;    # note backticks, qw"cat ..." also works
DaleJ
источник
-2

Вы можете использовать cat в Linux:

@file1=\`cat /etc/file.txt\`;
user1474509
источник