Em Perl, como posso ler um arquivo inteiro em uma string?

118

Estou tentando abrir um arquivo .html como uma grande string longa. Isso é o que eu tenho:

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

o que resulta em:

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

No entanto, quero que o resultado seja semelhante a:

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

Dessa forma, posso pesquisar todo o documento com mais facilidade.

goddamnyouryan
fonte
8
Realmente deveria verificar qual é a definição de "Não é possível instalar", é um problema comum e geralmente é um argumento que não precisa ser feito. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric
1
Na verdade, não consigo modificar nada em todo o servidor em que este script está sendo executado, exceto o próprio script.
goddamnyouryan
Então você não tem permissão para adicionar nenhum arquivo, em qualquer lugar do servidor?
Brad Gilbert
Módulos FatPack em seu script? Além disso, parece que você está pensando em analisar HTML com expressões regulares, não.
MkV

Respostas:

81

Adicionar:

 local $/;

antes de ler a partir do identificador de arquivo. Consulte Como posso ler um arquivo inteiro de uma vez? , ou

$ perldoc -q "arquivo inteiro"

Consulte Variáveis ​​relacionadas a filehandles em perldoc perlvare perldoc -f local.

Aliás, se você puder colocar seu script no servidor, poderá ter todos os módulos que desejar. Consulte Como mantenho meu próprio diretório de módulo / biblioteca? .

Além disso, Path :: Class :: File permite que você slurp e vomite .

Path :: minúsculo dá ainda mais métodos de conveniência, como slurp, slurp_raw,slurp_utf8 bem como os seus spewhomólogos.

Sinan Ünür
fonte
33
Você provavelmente deve explicar quais efeitos localizar $ / fará, bem como qual é seu propósito.
Danny
12
Se você não vai explicar nada sobre localização $/, provavelmente deve adicionar links para mais informações.
Brad Gilbert
7
Uma boa explicação passo a passo do que está fazendo: {local $ /; <$ fh>} é fornecido aqui: perlmonks.org/?node_id=287647
dawez
Talvez apenas diga por que você deve usar locale não my.
Geremia
@Geremia Uma discussão sobre o escopo está além do escopo desta resposta.
Sinan Ünür
99

Eu faria assim:

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

Observe o uso da versão de três argumentos de open. É muito mais seguro do que as versões antigas de dois (ou um) argumentos. Observe também o uso de um identificador de arquivo léxico. Manipuladores de arquivos lexicais são mais agradáveis ​​do que as variantes de palavras de barras antigas, por muitos motivos. Estamos aproveitando um deles aqui: fecham quando saem do escopo.

Chas. Owens
fonte
9
Esta é provavelmente a melhor maneira não-cpan de fazer isso, pois usa o argumento 3 aberto, bem como mantém a variável INPUT_RECORD_SEPARATOR ($ /) localizada no menor contexto necessário.
Danny
77

Com Arquivo :: Slurp :

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

Sim, até você pode usar o CPAN .

Quentin
fonte
O OP disse que não pode modificar nada no servidor. O link "Sim, até você pode usar o CPAN" aqui mostra como contornar essa limitação, na maioria dos casos.
Trenton
Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry
2
@Dmitry - Então instale o módulo. Há um link de instruções de instalação na página metacpan que vinculei a esta resposta.
Quentin de
53

Todas as postagens são ligeiramente não idiomáticas. O idioma é:

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

Geralmente, não há necessidade de definir $ / para undef.

Jrockway
fonte
3
local $foo = undefé apenas o método sugerido de Perl Best Practice (PBP). Se estamos postando trechos de código, acho que fazer o nosso melhor para deixar isso claro seria Uma coisa boa.
Danny
2
Mostrar às pessoas como escrever código não idiomático é uma coisa boa? Se eu visse "local $ / = undef" no código em que estava trabalhando, minha primeira ação seria humilhar publicamente o autor no irc. (E geralmente não sou exigente quanto a questões de "estilo".)
jrockway
1
Ok, vou morder: o que exatamente é digno de simulação sobre "local $ / = undef"? Se sua única resposta for "Não é idiomático", então (a) não tenho tanta certeza e (b) e daí? Não tenho tanta certeza, porque é terrivelmente comum como forma de fazer isso. E daí porque é perfeitamente claro e razoavelmente breve. Você pode ser mais exigente quanto às questões de estilo do que pensa.
Telêmaco
1
A chave é que o "$ / local" faz parte de um idioma bem conhecido. Se você está escrevendo algum código aleatório e escreve "local $ Foo :: Bar = undef;", tudo bem. Mas, neste caso muito especial, você pode muito bem falar a mesma língua que todo mundo, mesmo que seja "menos claro" (com o que não concordo; o comportamento de "local" é bem definido a esse respeito).
jrockway
11
Desculpe, discordo. É muito mais comum ser explícito quando você deseja alterar o comportamento real de uma variável mágica; é uma declaração de intenções. Até mesmo a documentação usa 'local $ / = undef' (veja perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera
19

De perlfaq5: Como posso ler um arquivo inteiro de uma vez? :


Você pode usar o módulo File :: Slurp para fazer isso em uma etapa.

use File::Slurp;

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

A abordagem usual do Perl para processar todas as linhas em um arquivo é fazer uma linha por vez:

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

Isso é tremendamente mais eficiente do que ler o arquivo inteiro na memória como uma matriz de linhas e, em seguida, processá-lo um elemento por vez, o que geralmente - senão quase sempre - é a abordagem errada. Sempre que você vir alguém fazer isso:

@lines = <INPUT>;

você deve pensar muito sobre por que precisa de tudo carregado de uma vez. Não é apenas uma solução escalonável. Você também pode achar mais divertido usar o módulo Tie :: File padrão ou as ligações $ DB_RECNO do módulo DB_File, que permitem vincular um array a um arquivo para que, ao acessar um elemento, o array realmente acesse a linha correspondente no arquivo .

Você pode ler todo o conteúdo do filehandle em um escalar.

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

Isso desfaz temporariamente o separador de registro e fecha automaticamente o arquivo na saída do bloco. Se o arquivo já estiver aberto, basta usar este:

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

Para arquivos comuns, você também pode usar a função de leitura.

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

O terceiro argumento testa o tamanho do byte dos dados no filehandle INPUT e lê esse número de bytes no buffer $ var.

brian d foy
fonte
8

Uma maneira simples é:

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

Outra forma é alterar o separador de registro de entrada "$ /". Você pode fazer isso localmente em um bloco vazio para evitar alterar o separador de registro global.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
Peter Mortensen
fonte
1
Há um número significativo de problemas com os dois exemplos que você deu. O principal problema é que eles são escritos em Perl antigo, eu recomendaria ler Perl Moderno
Brad Gilbert
@Brad, o comentário foi feito anos atrás, mas a questão ainda permanece. melhor é{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger
@Joel isso é apenas um pouco melhor. Você não verificou a saída de openou a chamada implicitamente close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Isso ainda tem o problema de não especificar a codificação de entrada.)
Brad Gilbert
use autodie, a principal melhoria que eu pretendia mostrar foi o lexical filehandle e o 3 arg open. Existe algum motivo para você estar dofazendo isso? por que não simplesmente despejar o arquivo em uma variável declarada antes do bloco?
Joel Berger
7

Defina $/como undef(veja a resposta de jrockway) ou apenas concatene todas as linhas do arquivo:

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

É recomendado usar escalares para filehandles em qualquer versão Perl que o suporte.

kixx
fonte
4

Outra forma possível:

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

Você só está obtendo a primeira linha do operador diamante <FILE>porque o está avaliando no contexto escalar:

$document = <FILE>; 

No contexto de lista / matriz, o operador diamante retornará todas as linhas do arquivo.

@lines = <FILE>;
print @lines;
Nathan
fonte
1
Apenas uma nota sobre a nomenclatura: o operador da nave é <=>e o <>é o operador diamante.
2010
Oh, obrigado, eu não tinha ouvido "operador de diamante" antes e pensei que ambos compartilhavam o mesmo nome. Vou corrigi-lo acima.
Nathan
2

Eu faria isso da maneira mais simples, para que qualquer pessoa pudesse entender o que acontece, mesmo que existam maneiras mais inteligentes:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
Alguma coisa alguma coisa
fonte
Todas essas concatenações de strings serão muito caras. Eu evitaria fazer isso. Por que separar os dados apenas para juntá-los novamente?
andru
2
open f, "test.txt"
$file = join '', <f>

<f>- retorna um array de linhas de nosso arquivo (se $/tiver o valor padrão "\n") e então join ''colocará este array em.

Тима Епанчинцев
fonte
2

Esta é mais uma sugestão de como NÃO fazer isso. Acabei de encontrar um bug em um aplicativo Perl bastante grande. A maioria dos módulos tinha seus próprios arquivos de configuração. Para ler os arquivos de configuração como um todo, encontrei esta única linha do Perl em algum lugar da Internet:

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

Ele reatribui o separador de linha conforme explicado anteriormente. Mas também reatribui o STDIN.

Isso teve pelo menos um efeito colateral que me custou horas para descobrir: ele não fecha o identificador de arquivo implícito corretamente (uma vez que não chama close nada).

Por exemplo, fazer isso:

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

resulta em:

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

O estranho é que o contador de linha $.é aumentado para cada arquivo em um. Não é redefinido e não contém o número de linhas. E não é redefinido para zero ao abrir outro arquivo até que pelo menos uma linha seja lida. No meu caso, eu estava fazendo algo assim:

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

Por causa desse problema, a condição era falsa porque o contador de linha não foi redefinido corretamente. Não sei se isso é um bug ou simplesmente um código errado ... Também chamar close;oder close STDIN;não ajuda.

Substituí esse código ilegível usando open, string concatenation e close. No entanto, a solução postada por Brad Gilbert também funciona, pois usa um identificador de arquivo explícito.

As três linhas no início podem ser substituídas por:

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

que fecha corretamente o identificador de arquivo.

mandíbula
fonte
2

Usar

 $/ = undef;

antes $document = <FILE>;. $/é o separador de registro de entrada , que é uma nova linha por padrão. Ao redefini-lo para undef, você está dizendo que não há separador de campo. Isso é chamado de modo "slurp".

Outras soluções como undef $/e local $/(mas não my $/) redeclaram $ / e, portanto, produzem o mesmo efeito.

Geremia
fonte
0

Você pode simplesmente criar uma sub-rotina:

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

Não sei se é uma boa prática, mas costumava usar isso:

($a=<F>);
zawy
fonte
-1

Todas essas são boas respostas. MAS se você está com preguiça e o arquivo não é tão grande e a segurança não é um problema (você sabe que não tem um nome de arquivo corrompido), então você pode dizer:

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

Você pode usar cat no Linux:

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