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.


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.


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.


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 .


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.


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.


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

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.


4

Otra forma posible:

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

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;

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

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.


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.


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.


0

Simplemente podría crear una subrutina:

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

0

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

($a=<F>);

-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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.