В Perl, как я могу прочитать весь файл в строку?

97

Я пытаюсь открыть файл .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
    На самом деле я не могу ничего изменить на всем сервере, на котором работает этот скрипт, кроме самого скрипта.
Показать ещё 2 комментария
Теги:
string

17 ответов

72
Лучший ответ

Добавить

 local $/;

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

$ perldoc -q "entire file"

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

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

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

Path::Tiny дает еще более удобные методы, такие как slurp, slurp_raw, slurp_utf8, а также их spew.

  • 30
    Вам, вероятно, следует объяснить, какие эффекты будет оказывать локализация $ /, а также какова ее цель.
  • 11
    Если вы не собираетесь объяснять что-либо о локализации $/ , вам, вероятно, следует добавить ссылки для получения дополнительной информации.
Показать ещё 5 комментариев
91

Я бы сделал это следующим образом:

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

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

  • 9
    Вероятно, это лучший не-cpan-способ сделать это, так как он использует как открытый 3 аргумента, так и сохраняя переменную INPUT_RECORD_SEPARATOR ($ /) локализованной в наименьшем требуемом контексте.
69

С File::Slurp:

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

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

  • 0
    отлично, сэкономь мое время
  • 0
    ОП сказал, что не может ничего изменить на сервере. Ссылка «Да, даже вы можете использовать CPAN» здесь показывает вам, как обойти это ограничение, в большинстве случаев.
Показать ещё 2 комментария
43

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

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

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

  • 3
    local $foo = undef - это метод, рекомендуемый Perl Best Practice (PBP). Если мы публикуем фрагменты кода, я думаю, что сделать все возможное, чтобы сделать его понятным, было бы хорошо.
  • 2
    Показывать людям, как писать неидиоматический код - это хорошо? Если бы я увидел «local $ / = undef» в коде, над которым я работал, моим первым действием было бы публичное унижение автора в irc. (И я вообще не разборчив в вопросах "стиля".)
Показать ещё 4 комментария
18

Из 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_File модулей $DB_RECNO привязок, которые позволяют привязать массив к файлу, чтобы доступ к элементу, который массив фактически обращается к соответствующей строке в файле.

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

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

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

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

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

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

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

8

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

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

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

7

Простым способом является:

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

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

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
  • 1
    Существует большое количество проблем с обоими примерами, которые вы привели. Основная проблема в том, что они написаны на древнем Perl, я бы порекомендовал прочитать Modern Perl
  • 0
    @Brad, комментарий был сделан много лет назад, но все еще остается в силе. лучше {local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Показать ещё 2 комментария
4

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

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;
3
open f, "test.txt"
$file = join '', <f>

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

3

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

$document = <FILE>; 

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

@lines = <FILE>;
print @lines;
  • 1
    Просто примечание по номенклатуре: оператор космического корабля - <=> а <> - оператор бриллианта.
  • 0
    О, спасибо, я раньше не слышал "оператор бриллиантов" и думал, что у них обоих одно имя. Я исправлю это выше.
2

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

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
  • 0
    Все эти объединения строк будут довольно дорогими. Я бы избегал этого. Зачем разрывать данные только для того, чтобы собрать их вместе?
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

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

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

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

Я заменил этот нечитаемый код, используя open, string concatenation и 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};

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

1

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

 $/ = undef;

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

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

1

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

#Get File Contents
sub gfc
{
    open FC, @_[0];
    join '', <FC>;
}
0

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

($a=<F>);
0

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

$x=`cat /tmp/foo`;    # note backticks, qw"cat ..." also works
-1

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

@file1=\`cat /etc/file.txt\`;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню