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.


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.


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.


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 .


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.


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.


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

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.


4

Outra forma possível:

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

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;

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

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.


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.


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.


0

Você pode simplesmente criar uma sub-rotina:

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

0

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

($a=<F>);

-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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.