Як я можу прочитати весь файл у рядку в 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
Дійсно, слід перевірити, що таке визначення "встановити Cant", його загальна проблема та загальний аргумент, який не потрібно робити. stackoverflow.com/questions/755168/perl-myths/…
Кент Фредрік

1
Я фактично не в змозі змінити нічого в цілому, наскільки цей сценарій працює, окрім сценарію, який я створюю сам.
Goddamnyouryan

Тож вам заборонено додавати файли ніде на сервері?
Бред Гілберт

Модулі FatPack у ваш сценарій? Крім того, схоже, ви можете думати про розбір HTML за допомогою регулярних виразів, не варто.
MkV

Відповіді:


81

Додати:

 local $/;

перед читанням з ручки файлу. Див. Як я можу прочитати у цілому файлі все одночасно? , або

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

Див. Змінні, що відносяться до файлових файлів у perldoc perlvarта perldoc -f local.

До речі, якщо ви можете розмістити свій скрипт на сервері, ви можете мати всі необхідні модулі. Див. Як я можу зберігати власний каталог модулів / бібліотек? .

Крім того, Path :: Class :: File дозволяє базікати і бачити .

Шлях :: Крихітка дає ще більш зручні методи , такі як slurp, slurp_raw,slurp_utf8 а також їх spewколеги.


33
Вам, мабуть, слід пояснити, які ефекти, що локалізують $ / збираються зробити, а також, яка його мета.
Danny

12
Якщо ви нічого не збираєтесь пояснювати щодо локалізації $/, вам, мабуть, слід додати посилання для отримання додаткової інформації.
Бред Гілберт

7
Хороше покрокове пояснення того, що робиться: {local $ /; <$ fh>} надано тут: perlmonks.org/?node_id=287647
dawez

Можливо, просто скажіть, чому потрібно використовувати, localа ні my.
Геремія

@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. Це набагато безпечніше, ніж старі дво- (або одно-) аргументовані версії. Також зверніть увагу на використання лексичного файлового файлу. Лексичні файлові вказівки приємніші, ніж старі варіанти барева, з багатьох причин. Ми тут скористаємося одним із них: вони закриваються, коли виходять із сфери застосування.


9
Це, мабуть, найкращий не-cpan'd спосіб зробити це, оскільки він використовує як три аргументи відкритими, так і зберігаючи змінну INPUT_RECORD_SEPARATOR ($ /), локалізовану на найменшому необхідному контексті.
Денні

77

ОП заявила, що не може нічого змінити на сервері. Посилання "Так, навіть ви можете використовувати 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> };

Здебільшого, не потрібно встановлювати $ / to undef.


3
local $foo = undefце лише запропонований метод найкращої практики Perl (PBP). Якщо ми публікуємо фрагменти коду, я б подумав, що робити все можливе, щоб було зрозуміло, що це буде гарною справою.
Danny

2
Показувати людям, як писати неідіоматичний код - це добре? Якби я бачив "local $ / = undef" у коді, над яким я працював, першою моєю дією було б публічно принизити автора на irc. (І я, як правило, не вибагливий щодо "стильових" питань.)
jrockway

1
Гаразд, я покусаю: чого саме знущається над "local $ / = undef"? Якщо Ваша єдина відповідь: "Це не ідіоматично", то (а) я не такий впевнений і (б), і що? Я не настільки впевнений, тому що це жахливо звичайно як спосіб зробити це. І що робити, тому що це абсолютно чітко і досить коротко. Ви можете бути більш вибагливими до питань стилю, які ви думаєте.
Телемах

1
Ключовим є те, що "локальний $ /" є частиною відомої ідіоми. Якщо ви пишете якийсь випадковий код і пишете "local $ Foo :: Bar = undef;", це добре. Але в цьому дуже особливому випадку ви можете також говорити тією ж мовою, що і всі інші, навіть якщо це "менш зрозуміло" (з чим я не згоден; поведінка "місцевих" в цьому відношенні чітко визначена).
jrockway

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, коментар був зроблений роками тому, однак все ще стоїть річ. краще{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Джоел Бергер

@Joel, це лише трохи краще. Ви не перевірили вихід openабо неявно викликаний close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (У цьому все ще є проблема, що він не вказує кодування входу.)
Бред Гілберт

use autodie, головне вдосконалення, яке я мав намір показати, було лексичним файловим файлом та 3 аргументацією. Чи є якась причина у вас doце? чому б не просто скинути файл у змінну, оголошену перед блоком?
Джоель Бергер

7

Або набір $/для undef(див відповіді jrockway) , або просто зчепити всі рядки до файлу:

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

Рекомендується використовувати скаляри для файлових файлів у будь-якій версії Perl, яка його підтримує.



3

Перший рядок ви отримуєте від оператора алмазів, <FILE>тому що оцінюєте його у скалярному контексті:

$document = <FILE>; 

У контексті списку / масиву оператор алмазів поверне всі рядки файлу.

@lines = <FILE>;
print @lines;

1
Лише примітка про номенклатуру: оператор космічного корабля є <=>і <>є оператором алмазів.
інструментарій

О, дякую, я раніше не чув "оператора з діамантами" і думав, що вони обоє мають одне ім'я. Я виправлю це вище.
Натан

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;одер close STDIN;не допомагає.

Я замінив цей нечитабельний код, використовуючи відкрите, рядкове з'єднання та закриття. Однак рішення, розміщене Бредом Гілбертом, також працює, оскільки замість нього використовується явна ручка файлів.

Три рядки на початку можна замінити на:

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 $/), переоформлюють $ / і таким чином дають той же ефект.



0

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

($a=<F>);

-1

Це все хороші відповіді. АЛЕ, якщо ви лінуєтесь, і файл не такий великий, і безпека не є проблемою (ви знаєте, що у вас немає запхнутого імені файлу), тоді ви можете розкрити:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.