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

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 = ; 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 parecido com:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

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

Adicionar:

  local $/; 

antes de ler a partir do identificador de arquivo. Veja Como posso ler um arquivo inteiro de uma só vez? ou

  $ perldoc -q "arquivo inteiro" 

Veja Variáveis ​​relacionadas a filehandles em perldoc perlvar e perldoc -f local .

Aliás, se você pode colocar seu script no servidor, você pode ter todos os módulos que você deseja. Veja Como eu mantenho meu próprio diretório de módulos / bibliotecas? .

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

Path :: Tiny dá ainda mais methods de conveniência, como slurp , slurp_raw , slurp_utf8 , bem como seus spew homólogos.

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 aberto. É muito mais seguro que as versões antigas de dois (ou um) argumento. Observe também o uso de um filehandle léxico. As alças de arquivos lexicais são mais agradáveis ​​do que as antigas variantes de bareword, por vários motivos. Estamos aproveitando um deles aqui: eles fecham quando saem do escopo.

Com o File :: Slurp :

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

Sim, até você pode usar o CPAN .

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

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

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

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


Você pode usar o módulo File :: Slurp para fazer isso em uma única 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 () { chomp; # do something with $_ } close(INPUT) || die "can't close $file: $!"; 

Isso é tremendamente mais eficiente do que ler todo o arquivo na memory como uma matriz de linhas e, em seguida, processá-lo um elemento por vez, o que geralmente é – se não quase sempre – a abordagem errada. Sempre que você vê alguém fazer isso:

 @lines = ; 

Você deve pensar muito sobre por que você precisa de tudo carregado de uma só vez. Não é apenas uma solução escalá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 amarrar uma matriz a um arquivo para que, ao acessar um elemento, a matriz 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 = ; } 

Que temporariamente undefs seu separador de registro, e irá fechar automaticamente o arquivo na saída do bloco. Se o arquivo já estiver aberto, use isto:

 $var = do { local $/;  }; 

Para arquivos comuns, você também pode usar a function de leitura.

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

O terceiro argumento testa o tamanho do byte dos dados no filehandle INPUT e lê quantos bytes no buffer $ var.

Uma maneira simples é:

 while () { $document .= $_ } 

Outra maneira é alterar o separador de registro de input “$ /”. Você pode fazer isso localmente em um bloco simples para evitar alterar o separador de registro global.

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

Defina $/ para 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 do Perl que o suporte.

Outra maneira possível:

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

Você está recebendo apenas a primeira linha do operador de diamantes porque está avaliando em um contexto escalar:

 $document = ; 

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

 @lines = ; print @lines; 

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

 my $text = ""; while (my $line = ) { $text .= $line; } 
 open f, "test.txt" $file = join '',  

– retorna uma matriz de linhas do nosso arquivo (se $/ tem o valor padrão "\n" ) e, em seguida, a join '' colocará essa matriz em.

Esta é mais uma sugestão de como NÃO fazer isso. Acabei de ter um tempo ruim para 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 linha única de Perl em algum lugar na 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 encontrar: Ele não fecha o identificador de arquivo implícito corretamente (desde que ele não chama de close ).

Por exemplo, fazendo 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 () { 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 a linha contra $. é aumentado para cada arquivo por 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) {}; 

Devido a esse problema, a condição era falsa porque o contador de linha não foi redefinido corretamente. Eu não sei se isso é um bug ou simplesmente código errado … Também chamando de close; oder close STDIN; não ajuda.

Eu substitui esse código ilegível usando concatenação de cadeia aberta e fechamento. 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.

Usar

  $/ = undef; 

antes de $document = ; . $/ é o separador de registro de input , 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 $/ ) redeclare $ / local e, portanto, produzem o mesmo efeito.

Você poderia simplesmente criar uma sub-rotina:

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

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

 ($a=); 

Essas são todas boas respostas. MAS se você está se sentindo preguiçoso, e o arquivo não é tão grande, e a segurança não é um problema (você sabe que você não tem um nome de arquivo contaminado), então você pode desembolsar:

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

Você pode usar o gato no Linux:

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