En Perl, ¿cómo puedo leer un archivo completo en una cadena?

118

Estoy intentando abrir un archivo .html como una cadena larga y grande. Esto es lo que tengo:

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

lo que resulta en:

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

Sin embargo, quiero que el resultado se vea así:

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

De esta forma puedo buscar en todo el documento con mayor facilidad.

goddamnyouryan
fuente
8
Realmente debería verificar cuál es la definición de "No se puede instalar", es un problema común y comúnmente es un argumento que no necesita ser presentado. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric
1
De hecho, no puedo modificar nada en todo el servidor en el que se ejecuta este script, aparte del script en sí.
goddamnyouryan
Entonces, ¿no puede agregar archivos en ningún lugar del servidor?
Brad Gilbert
¿Módulos FatPack en su script? Además, parece que podría estar pensando en analizar HTML con expresiones regulares, no lo haga.
MkV

Respuestas:

81

Añadir:

 local $/;

antes de leer desde el identificador de archivo. Consulte ¿Cómo puedo leer un archivo completo de una vez? o

$ perldoc -q "archivo completo"

Consulte Variables relacionadas con identificadores de archivos en perldoc perlvary perldoc -f local.

Por cierto, si puede poner su script en el servidor, puede tener todos los módulos que desee. Consulte ¿Cómo mantengo mi propio directorio de módulos / bibliotecas? .

Además, Path :: Class :: File te permite sorber y escupir .

Path :: Tiny da aún más métodos de conveniencia como slurp, slurp_raw,slurp_utf8 así como sus spewhomólogos.

Sinan Ünür
fuente
33
Probablemente debería explicar qué efectos va a tener la localización de $ / y cuál es su propósito.
Danny
12
Si no va a explicar nada sobre la localización $/, probablemente debería agregar enlaces para obtener más información.
Brad Gilbert
7
Una buena explicación paso a paso de lo que está haciendo: {local $ /; <$ fh>} se proporciona aquí: perlmonks.org/?node_id=287647
dawez
Quizás solo diga por qué debe usar localy no my.
Geremia
@Geremia Una discusión sobre el alcance está más allá del alcance de esta respuesta.
Sinan Ünür
99

Lo haría así:

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

Tenga en cuenta el uso de la versión de tres argumentos de open. Es mucho más seguro que las versiones antiguas de dos (o uno) argumentos. También tenga en cuenta el uso de un identificador de archivo léxico. Los identificadores de archivos léxicos son más agradables que las antiguas variantes de palabras sin formato, por muchas razones. Estamos aprovechando uno de ellos aquí: cierran cuando se salen de alcance.

Chas. Owens
fuente
9
Esta es probablemente la mejor forma no cpanada de hacerlo, ya que usa tanto el argumento 3 abierto como mantiene la variable INPUT_RECORD_SEPARATOR ($ /) localizada en el contexto requerido más pequeño.
Danny
77

Con Archivo :: Slurp :

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

Sí, incluso tú puedes usar CPAN .

Quentin
fuente
El OP dijo que no puede modificar nada en el servidor. El enlace "Sí, incluso usted puede usar CPAN" aquí le muestra cómo evitar esa limitación, en la mayoría de los casos.
Trenton
Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry
2
@Dmitry: instale el módulo. Hay un enlace de instrucciones de instalación en la página de metacpan a la que me vinculé desde esta respuesta.
Quentin
53

Todas las publicaciones son un poco no idiomáticas. El modismo es:

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

En general, no es necesario establecer $ / to undef.

jrockway
fuente
3
local $foo = undefes solo el método sugerido por Perl Best Practice (PBP). Si publicamos fragmentos de código, creo que hacer todo lo posible para dejarlo claro sería algo bueno.
Danny
2
¿Mostrar a la gente cómo escribir código no idiomático es algo bueno? Si veía "local $ / = undef" en el código en el que estaba trabajando, mi primera acción sería humillar públicamente al autor en irc. (Y, en general, no soy exigente con los problemas de "estilo".)
jrockway
1
Ok, voy a morder: ¿qué es exactamente digno de burlarse de "local $ / = undef"? Si su única respuesta es "No es idiomático", entonces (a) no estoy tan seguro y (b) ¿y qué? No estoy tan seguro, porque es muy común como una forma de hacer esto. Y qué, porque es perfectamente claro y razonablemente breve. Puede ser más exigente con los problemas de estilo que cree.
Telemachus
1
La clave es que el "$ / local" es parte de un idioma muy conocido. Si está escribiendo un código aleatorio y escribe "local $ Foo :: Bar = undef;", está bien. Pero en este caso tan especial, también podría hablar el mismo idioma que todos los demás, incluso si es "menos claro" (con lo que no estoy de acuerdo; el comportamiento de "local" está bien definido a este respecto).
jrockway
11
Lo siento, no estoy de acuerdo. Es mucho más común ser explícito cuando desea cambiar el comportamiento real de una variable mágica; es una declaración de intenciones. Incluso la documentación usa 'local $ / = undef' (ver perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera
19

De perlfaq5: ¿Cómo puedo leer un archivo completo de una vez? :


Puede usar el módulo File :: Slurp para hacerlo en un solo paso.

use File::Slurp;

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

El enfoque habitual de Perl para procesar todas las líneas de un archivo es hacerlo una línea a la vez:

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

Esto es tremendamente más eficiente que leer todo el archivo en la memoria como una matriz de líneas y luego procesarlo un elemento a la vez, lo que a menudo, si no casi siempre, es el enfoque incorrecto. Siempre que veas a alguien hacer esto:

@lines = <INPUT>;

debe pensar detenidamente por qué necesita todo cargado a la vez. Simplemente no es una solución escalable. También puede que le resulte más divertido utilizar el módulo estándar Tie :: File, o los enlaces $ DB_RECNO del módulo DB_File, que le permiten vincular una matriz a un archivo para que, al acceder a un elemento, la matriz acceda a la línea correspondiente del archivo. .

Puede leer todo el contenido del identificador de archivo en un escalar.

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

Eso anula temporalmente su separador de registros y cerrará automáticamente el archivo al salir del bloque. Si el archivo ya está abierto, simplemente use esto:

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

Para archivos normales, también puede utilizar la función de lectura.

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

El tercer argumento prueba el tamaño de bytes de los datos en el identificador de archivo INPUT y lee esa cantidad de bytes en el búfer $ var.

brian d foy
fuente
8

Una forma sencilla es:

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

Otra forma es cambiar el separador de registros de entrada "$ /". Puede hacerlo localmente en un bloque simple para evitar cambiar el separador de registros global.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
Peter Mortensen
fuente
1
Hay una cantidad significativa de problemas con los dos ejemplos que dio. El principal problema es que están escritos en Perl antiguo, recomendaría leer Modern Perl
Brad Gilbert
@Brad, el comentario se hizo hace años, sin embargo, el punto sigue en pie. mejor es{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger
@Joel eso es solo un poco mejor. No verificó la salida de openo el llamado implícitamente close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Eso todavía tiene el problema de que no especifica la codificación de entrada.)
Brad Gilbert
use autodie, la principal mejora que quise mostrar fue el identificador de archivo léxico y el 3 arg abierto. ¿Hay alguna razón por la que estás dohaciendo esto? ¿Por qué no volcar el archivo en una variable declarada antes del bloque?
Joel Berger
7

Puede configurarlo $/en undef(ver la respuesta de jrockway) o simplemente concatenar todas las líneas del archivo:

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

Se recomienda utilizar escalares para identificadores de archivos en cualquier versión de Perl que lo admita.

kixx
fuente
4

Otra forma posible:

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

Solo obtiene la primera línea del operador de diamante <FILE>porque la está evaluando en un contexto escalar:

$document = <FILE>; 

En el contexto de lista / matriz, el operador de diamante devolverá todas las líneas del archivo.

@lines = <FILE>;
print @lines;
Nathan
fuente
1
Solo una nota sobre la nomenclatura: el operador de la nave espacial es <=>y el <>es el operador de diamante.
toolic
Oh, gracias, no había escuchado "operador de diamantes" antes y pensé que ambos compartían el mismo nombre. Lo corregiré arriba.
Nathan
2

Lo haría de la manera más simple, para que cualquiera pueda entender lo que sucede, incluso si hay formas más inteligentes:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
Algo algo
fuente
Todas esas concatenaciones de cadenas van a ser bastante caras. Evitaría hacer esto. ¿Por qué separar los datos solo para volver a unirlos?
andru
2
open f, "test.txt"
$file = join '', <f>

<f>- devuelve una matriz de líneas de nuestro archivo (si $/tiene el valor predeterminado "\n") y luego join ''pegará esta matriz.

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

Esto es más una sugerencia sobre cómo NO hacerlo. Lo he pasado mal para encontrar un error en una aplicación Perl bastante grande. La mayoría de los módulos tenían sus propios archivos de configuración. Para leer los archivos de configuración en su conjunto, encontré esta única línea de Perl en algún lugar de Internet:

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

Reasigna el separador de línea como se explicó anteriormente. Pero también reasigna el STDIN.

Esto tuvo al menos un efecto secundario que me costó horas encontrarlo: no cierra correctamente el identificador de archivo implícito (ya que no llama closeen absoluto).

Por ejemplo, haciendo eso:

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 en:

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

Lo extraño es que el contador de líneas $.aumenta en uno para cada archivo. No se restablece y no contiene el número de líneas. Y no se restablece a cero al abrir otro archivo hasta que se lee al menos una línea. En mi caso, estaba haciendo algo como esto:

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

Debido a este problema, la condición era falsa porque el contador de línea no se restableció correctamente. No sé si esto es un error o simplemente un código incorrecto ... Además, llamar a close;oder close STDIN;no ayuda.

Reemplacé este código ilegible usando abrir, concatenación de cadenas y cerrar. Sin embargo, la solución publicada por Brad Gilbert también funciona, ya que utiliza un identificador de archivo explícito.

Las tres líneas al principio se pueden reemplazar 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 cierra correctamente el identificador del archivo.

mandíbula
fuente
2

Utilizar

 $/ = undef;

antes $document = <FILE>;. $/es el separador de registros de entrada , que es una nueva línea por defecto. Al redefinirlo a undef, está diciendo que no hay un separador de campo. Esto se llama modo "sorber".

Otras soluciones como undef $/y local $/(pero no my $/) redeclaran $ / y por lo tanto producen el mismo efecto.

Geremia
fuente
0

Simplemente podría crear una subrutina:

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

No sé si es una buena práctica, pero solía usar esto:

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

Todas estas son buenas respuestas. PERO si se siente perezoso y el archivo no es tan grande, y la seguridad no es un problema (sabe que no tiene un nombre de archivo contaminado), entonces puede pagar:

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

Puedes usar cat en Linux:

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