W Perlu, jak mogę wczytać cały plik do łańcucha?


118

Próbuję otworzyć plik .html jako jeden duży długi ciąg. Oto co mam:

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

Co skutkuje w:

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

Jednak chcę, aby wynik wyglądał następująco:

<!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">

W ten sposób mogę łatwiej przeszukiwać cały dokument.


8
Naprawdę należy sprawdzić, jaka jest definicja „Nie można zainstalować”, jest to powszechny problem i często jest to argument, którego nie trzeba przedstawiać. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric

1
Właściwie nie jestem w stanie niczego zmodyfikować na całym serwerze, na którym działa ten skrypt, z wyjątkiem samego skryptu.
goddamnyouryan

Więc nie możesz dodawać żadnych plików w dowolnym miejscu na serwerze?
Brad Gilbert,

Moduły FatPack do twojego skryptu? Wygląda również na to, że myślisz o parsowaniu kodu HTML za pomocą wyrażeń regularnych, nie rób tego.
MkV

Odpowiedzi:


82

Dodaj:

 local $/;

przed odczytaniem z uchwytu pliku. Zobacz: Jak mogę wczytać cały plik naraz? lub

$ perldoc -q "cały plik"

Zobacz Zmienne związane z uchwytami plików w perldoc perlvari perldoc -f local.

Nawiasem mówiąc, jeśli możesz umieścić swój skrypt na serwerze, możesz mieć wszystkie potrzebne moduły. Zobacz Jak mogę zachować własny katalog modułów / bibliotek? .

Ponadto Ścieżka :: Klasy :: File pozwala slurp i rzygać .

Ścieżka :: Tiny daje jeszcze więcej wygody metod takich jak slurp, slurp_raw,slurp_utf8 a także ich spewodpowiedniki.


33
Powinieneś prawdopodobnie wyjaśnić, jakie efekty przyniesie lokalizacja $ / i jaki jest jego cel.
Danny

12
Jeśli nie zamierzasz niczego wyjaśniać na temat lokalizacji $/, prawdopodobnie powinieneś dodać linki do dalszych informacji.
Brad Gilbert,

7
Dobre wyjaśnienie, krok po kroku, co się dzieje: {local $ /; <$ fh>} warunkiem jest tutaj: perlmonks.org/?node_id=287647
dawez

Może po prostu powiedz, dlaczego musisz używać, locala nie my.
Geremia

@Geremia Omówienie zakresu wykracza poza zakres tej odpowiedzi.
Sinan Ünür

99

Zrobiłbym to tak:

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

Zwróć uwagę na użycie trzyargumentowej wersji open. Jest znacznie bezpieczniejszy niż stare wersje dwu- (lub jedno-) argumentowe. Zwróć również uwagę na użycie leksykalnego uchwytu pliku. Leksykalne uchwyty plików są ładniejsze niż stare warianty gołego słowa z wielu powodów. Wykorzystujemy tutaj jeden z nich: zamykają się, gdy wychodzą poza zakres.


9
Jest to prawdopodobnie najlepszy sposób na zrobienie tego bez cpan, ponieważ wykorzystuje oba argumenty otwarte, a także utrzymuje zmienną INPUT_RECORD_SEPARATOR ($ /) zlokalizowaną w najmniejszym wymaganym kontekście.
Danny

77

Z plikiem :: Slurp :

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

Tak, nawet ty możesz używać CPAN .


OP powiedział, że nie może niczego modyfikować na serwerze. Łącze „Tak, nawet ty możesz używać CPAN” tutaj pokazuje, jak obejść to ograniczenie w większości przypadków.
Trenton

Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry,

2
@Dmitry - Więc zainstaluj moduł. Na stronie metacpan, do której łącze znajduje się w tej odpowiedzi, znajduje się łącze do instrukcji instalacji.
Quentin

53

Wszystkie posty są nieco nieidiomatyczne. Idiom to:

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

Przeważnie nie ma potrzeby ustawiania $ / to undef.


3
local $foo = undefjest po prostu metodą sugerowaną przez Perl Best Practice (PBP). Jeśli publikujemy fragmenty kodu, myślę, że zrobienie wszystkiego, co w naszej mocy, aby było jasne, byłoby dobrą rzeczą.
Danny

2
Pokazywanie ludziom, jak pisać kod nieidiomatyczny, to dobra rzecz? Gdybym zobaczył "local $ / = undef" w kodzie, nad którym pracowałem, moją pierwszą czynnością byłoby publiczne upokorzenie autora na irc. (I generalnie nie jestem wybredny w kwestiach „stylu”).
jrockway

1
Ok, ugryzę: co dokładnie jest godne pozorowania w „lokalnym $ / = undef”? Jeśli jedyną odpowiedzią jest „To nie idiomatyczne”, to (a) nie jestem tego taki pewien i (b) co z tego? Nie jestem tego taki pewien, ponieważ jest to cholernie powszechny sposób na zrobienie tego. I co z tego, ponieważ jest całkowicie jasny i dość krótki. Możesz być bardziej wybredny w kwestiach stylu, które myślisz.
Telemachus

1
Kluczem jest to, że „lokalny $ /” jest częścią dobrze znanego idiomu. Jeśli piszesz jakiś losowy kod i napiszesz „local $ Foo :: Bar = undef;”, to jest w porządku. Ale w tym bardzo szczególnym przypadku możesz równie dobrze mówić tym samym językiem, co wszyscy inni, nawet jeśli jest to „mniej jasne” (z czym się nie zgadzam; zachowanie „lokalnego” jest dobrze zdefiniowane w tym zakresie).
jrockway

11
Przepraszam, nie zgadzam się. O wiele bardziej powszechne jest wyraźne wyrażanie się, gdy chcesz zmienić rzeczywiste zachowanie magicznej zmiennej; jest to deklaracja intencji. Nawet dokumentacja używa 'local $ / = undef' (patrz perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera

19

Z perlfaq5: Jak mogę odczytać cały plik naraz? :


Możesz użyć modułu File :: Slurp, aby zrobić to w jednym kroku.

use File::Slurp;

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

Zwyczajowe podejście Perla do przetwarzania wszystkich wierszy w pliku polega na wykonywaniu tego po jednym wierszu na raz:

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

Jest to znacznie bardziej wydajne niż wczytywanie całego pliku do pamięci jako tablicy wierszy, a następnie przetwarzanie go po jednym elemencie na raz, co jest często - jeśli nie prawie zawsze - złym podejściem. Ilekroć zobaczysz, że ktoś to robi:

@lines = <INPUT>;

Powinieneś długo i intensywnie przemyśleć, dlaczego potrzebujesz wszystkiego załadowanego na raz. To po prostu nie jest skalowalne rozwiązanie. Możesz również uznać za zabawniejsze użycie standardowego modułu Tie :: File lub powiązań $ DB_RECNO modułu DB_File, które pozwalają ci powiązać tablicę z plikiem tak, że dostęp do elementu tablica faktycznie uzyskuje dostęp do odpowiedniej linii w pliku .

Możesz wczytać całą zawartość uchwytu pliku do wartości skalarnej.

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

To tymczasowo unieważnia twój separator rekordów i automatycznie zamyka plik przy wyjściu z bloku. Jeśli plik jest już otwarty, użyj tego:

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

W przypadku zwykłych plików możesz również użyć funkcji odczytu.

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

Trzeci argument sprawdza rozmiar bajtów danych w uchwycie pliku INPUT i wczytuje tę liczbę bajtów do bufora $ var.


8

Prosty sposób to:

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

Innym sposobem jest zmiana separatora rekordów wejściowych „$ /”. Możesz to zrobić lokalnie w czystym bloku, aby uniknąć zmiany globalnego separatora rekordów.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}

1
Z obydwoma podanymi przez ciebie przykładami wiąże się wiele problemów. Głównym problemem jest to, że są napisane w starożytnym Perlu, polecam przeczytanie Modern Perl
Brad Gilbert

@Brad, komentarz został złożony lata temu, jednak kwestia ta jest nadal aktualna. lepiej{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger

@Joel, który jest tylko trochę lepszy. Nie sprawdziłeś wyniku openlub niejawnie wywołanego close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Nadal ma problem z tym, że nie określa kodowania wejściowego.)
Brad Gilbert,

use autodie, głównym ulepszeniem, które chciałem pokazać, był leksykalny uchwyt pliku i otwarty 3 arg. Czy jest jakiś powód, dla którego to robisz do? dlaczego po prostu nie wrzucić pliku do zmiennej zadeklarowanej przed blokiem?
Joel Berger

7

Albo zestaw $/do undef(patrz odpowiedź jrockway użytkownika) lub po prostu złączyć wszystkie linie w pliku jest:

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

Zaleca się używanie skalarów do uchwytów plików w każdej wersji Perla, która to obsługuje.


4

Inny możliwy sposób:

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;

3

Otrzymujesz tylko pierwszą linię od operatora diamentu, <FILE>ponieważ oceniasz ją w kontekście skalarnym:

$document = <FILE>; 

W kontekście listy / tablicy operator rombu zwróci wszystkie wiersze pliku.

@lines = <FILE>;
print @lines;

1
Tylko uwaga na temat nomenklatury: operator statku kosmicznego jest, <=>a <>jest operatorem diamentu.
narzędziowy

Och, dzięki, nie słyszałem wcześniej „operatora diamentu” i pomyślałem, że mają to samo imię. Poprawię to powyżej.
Nathan,

2

Zrobiłbym to w najprostszy sposób, aby każdy mógł zrozumieć, co się dzieje, nawet jeśli są sprytniejsze sposoby:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}

Wszystkie te konkatenacje strun będą dość kosztowne. Unikałbym tego. Po co rozrywać dane tylko po to, by je złożyć z powrotem?
andru

2
open f, "test.txt"
$file = join '', <f>

<f>- zwraca tablicę linii z naszego pliku (jeśli $/ma wartość domyślną "\n"), a następnie join ''umieści tę tablicę w.


2

To bardziej sugestia, jak tego NIE robić. Po prostu ciężko mi było znaleźć błąd w dość dużej aplikacji Perla. Większość modułów miała własne pliki konfiguracyjne. Aby odczytać pliki konfiguracyjne jako całość, znalazłem gdzieś w Internecie ten pojedynczy wiersz Perla:

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

Ponownie przypisuje separator linii, jak wyjaśniono wcześniej. Ale również ponownie przypisuje STDIN.

Miało to co najmniej jeden efekt uboczny, którego znalezienie kosztowało mnie wiele godzin: nie zamyka prawidłowo niejawnego uchwytu pliku (ponieważ w ogóle nie wywołuje close).

Na przykład robiąc to:

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";

prowadzi do:

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

Dziwne jest to, że licznik linii $.jest zwiększany dla każdego pliku o jeden. Nie jest resetowany i nie zawiera liczby wierszy. I nie jest resetowany do zera podczas otwierania innego pliku, dopóki nie zostanie odczytany przynajmniej jeden wiersz. W moim przypadku robiłem coś takiego:

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

Z powodu tego problemu warunek był fałszywy, ponieważ licznik linii nie został poprawnie zresetowany. Nie wiem, czy to błąd, czy po prostu zły kod ... Również wywołanie close;odera close STDIN;nie pomaga.

Zastąpiłem ten nieczytelny kod, używając open, konkatenacji ciągów i close. Jednak rozwiązanie opublikowane przez Brada Gilberta działa również, ponieważ zamiast tego używa jawnego uchwytu pliku.

Trzy wiersze na początku można zastąpić:

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};

który prawidłowo zamyka uchwyt pliku.


2

Posługiwać się

 $/ = undef;

wcześniej $document = <FILE>;. $/to separator rekordów wejściowych , którym domyślnie jest znak nowej linii. Przedefiniowując go na undef, mówisz, że nie ma separatora pól. Nazywa się to trybem „slurp”.

Inne rozwiązania, takie jak undef $/i local $/(ale nie my $/) ponownie deklarują $ / i dają w ten sposób ten sam efekt.



0

Nie wiem, czy to dobra praktyka, ale używałem tego:

($a=<F>);

-1

To wszystko są dobre odpowiedzi. ALE jeśli czujesz się leniwy, a plik nie jest tak duży, a bezpieczeństwo nie jest problemem (wiesz, że nie masz skażonej nazwy pliku), możesz wyskoczyć:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.